import * as p2ast from "prismafile/dist/ast" ; import * as graph from "../prisma1/graph" ; import * as p2 from "../prisma2" ; import * as p1 from "../prisma1" ; import * as uri from "url" ; import * as sql from "../sql" ; type Input = { prisma1 : p1 . Schema ; prisma2 : p2 . Schema ; url : string ; } ; type Output = { schema : p2 . Schema ; warnings : string [ ] ; ops : sql . Op [ ] ; breakingOps : sql . Op [ ] ; idOps : sql . Op [ ] ; } ; export async function upgrade ( input ) { const { prisma1 , prisma2 , url } = input ; const pgSchema = getPGSchema ( url ) ; const provider = prisma2 . provider ( ) ; const postgreSQL = provider === "postgres" || provider === "postgresql" ; const warnings = [ ] ; let p1Enums = { } ; for ( let p1Enum of prisma1 . enums ) { p1Enums [ p1Enum . name ] = p1Enum ; } let p2Enums = { } ; for ( let p2Enum of prisma2 . enums ) { p2Enums [ p2Enum . name ] = p2Enum ; } const models = prisma1 . objects ; for ( let p1Model of models ) { const fields = p1Model . fields ; for ( let p2Model of prisma2 . models ) { if ( p2Model . name !== p1Model . dbname ) { continue ; } if ( p2Model . name !== p1Model . name ) { p2Model . rename ( p1Model . name ) ; } for ( let p1Field of fields ) { for ( let p2Field of p2Model . fields ) { if ( p2Field . name !== p1Field . dbname ) { continue ; } if ( p2Field . name !== p1Field . name ) { p2Field . rename ( p1Field . name ) ; } switch ( p1Field . type . named ( ) ) { case "ID" : p2Field . upsertAttribute ( defaultAttr ( cuid ( ) ) ) ; break ; case "UUID" : p2Field . upsertAttribute ( defaultAttr ( uuid ( ) ) ) ; break ; } for ( let p1Attr of p1Field . directives ) { if ( p1Attr . name === "updatedAt" ) { p2Field . upsertAttribute ( updatedAt ( ) ) ; } if ( isMySQLDefaultText ( provider , p1Field , p1Attr ) ) { const value = getDefaultValueString ( p1Attr ) ; if ( typeof value === "string" ) { p2Field . upsertAttribute ( defaultAttr ( { type : "string_value" , start : pos , end : pos , value : value , } ) ) ; } } if ( isMySQLDefaultJson ( provider , p1Field , p1Attr ) ) { warnings . push ( `Prisma 2.0 doesn't support the Json data type with a default yet.

                See: https://github.com/prisma/prisma/issues/2556 for more information.` ) ; continue ; } } } } } } const ops = [ ] ; const breakingOps = [ ] ; for ( let p1Model of prisma1 . objects ) { const p2Model = prisma2 . findModel ( ( m ) => m . name === p1Model . name ) ; if ( ! p2Model ) { continue ; } for ( let p1Field of p1Model . fields ) { const p2Field = p2Model . findField ( ( f ) => f . name === p1Field . name ) ; if ( ! p2Field ) { continue ; } if ( p1Field . type . named ( ) === "Json" && ! isJsonType ( p2Field ) ) { if ( postgreSQL ) { breakingOps . push ( { type : "SetJsonTypeOp" , schema : pgSchema , p1Model , p1Field , } ) ; } else { ops . push ( { type : "SetJsonTypeOp" , schema : pgSchema , p1Model , p1Field , } ) ; } } const p1Enum = p1Enums [ p1Field . type . named ( ) ] ; const p2Enum = p2Enums [ p2Field . type . innermost ( ) . toString ( ) ] ; if ( p1Enum && ! p2Enum ) { if ( postgreSQL ) { breakingOps . push ( { type : "SetEnumTypeOp" , schema : pgSchema , p1Model , p1Field , p1Enum , } ) ; } else { ops . push ( { type : "SetEnumTypeOp" , schema : pgSchema , p1Model , p1Field , p1Enum , } ) ; } } for ( let p1Attr of p1Field . directives ) { if ( p1Attr . name === "default" ) { const p1Arg = p1Attr . findArgument ( ( a ) => a . name === "value" ) ; if ( ! p1Arg ) { continue ; } const p2Attr = p2Field . findAttribute ( ( a ) => a . name === "default" ) ; if ( p2Attr && hasExpectedDefault ( p1Arg , p2Attr . arguments [ 0 ] ) ) { continue ; } if ( isMySQLDefaultText ( provider , p1Field , p1Attr ) ) { continue ; } if ( isMySQLDefaultJson ( provider , p1Field , p1Attr ) ) { continue ; } ops . push ( { type : "SetDefaultOp" , schema : pgSchema , p1Model , p1Field , p1Attr , p1Enum , } ) ; } if ( p1Attr . name === "createdAt" ) { if ( ! hasDefaultNow ( p2Field ) ) { ops . push ( { type : "SetCreatedAtOp" , schema : pgSchema , p1Model , p1Field , p1Attr , } ) ; } p2Field . upsertAttribute ( defaultNow ( ) ) ; } } } } if ( postgreSQL ) { for ( let p1Model of prisma1 . objects ) { const p2Model = prisma2 . findModel ( ( m ) => m . name === p1Model . name ) ; if ( ! p2Model ) { continue ; } for ( let p1Field of p1Model . fields ) { if ( ! p1Field . type . list ( ) ) { continue ; } const p2Field = p2Model . findField ( ( f ) => f . name === p1Field . name ) ; if ( p2Field ) { continue ; } if ( p1Field . type . isReference ( ) ) { let p1Enum ; p1Enum = prisma1 . enums . find ( ( e ) => p1Field . type . named ( ) === e . name ) ; if ( ! p1Enum ) { continue ; } breakingOps . push ( { type : "MigrateEnumListOp" , schema : pgSchema , p1Model : p1Model , p1Field : p1Field , p1Enum : p1Enum , } ) ; } else { breakingOps . push ( { type : "MigrateScalarListOp" , schema : pgSchema , p1Model : p1Model , p1Field : p1Field , } ) ; } const p2ModelList = prisma2 . findModel ( ( m ) => { return m . name === `${ p1Model . name }_${ p1Field . name }` ; } ) ; if ( p2ModelList ) { p2ModelList . remove ( ) ; } } } } const g = graph . load ( prisma1 ) ; const edges = g . edges ( ) ; const relations = [ ] ; const named = { } ; for ( let i = 0 ; i < edges . length ; i ++ ) { const edge = g . edge ( edges [ i ] . v , edges [ i ] . w ) ; if ( ! edge . name ) { continue ; } if ( ! named [ edge . name ] ) { named [ edge . name ] = [ ] ; } named [ edge . name ] . push ( edge ) ; } for ( let k in named ) { if ( named [ k ] . length !== 2 ) { continue ; } relations . push ( [ named [ k ] [ 0 ] , named [ k ] [ 1 ] ] ) ; } const backrelations = { } ; for ( let i = 0 ; i < edges . length ; i ++ ) { const edge = g . edge ( edges [ i ] . v , edges [ i ] . w ) ; if ( edge . name ) { continue ; } const id = [ edge . from . name , edge . to . name ] . sort ( ) . join ( " " ) ; if ( ! backrelations [ id ] ) { backrelations [ id ] = [ ] ; } backrelations [ id ] . push ( edge ) ; } for ( let k in backrelations ) { if ( backrelations [ k ] . length !== 2 ) { continue ; } relations . push ( [ backrelations [ k ] [ 0 ] , backrelations [ k ] [ 1 ] ] ) ; } for ( let [ edge1 , edge2 ] of relations ) { if ( ! isInlineOneToOne ( edge1 , edge2 ) ) { continue ; } const uniqueEdge = findUniqueEdge ( edge1 , edge2 ) ; if ( ! isOneToOne ( prisma2 , uniqueEdge ) ) { ops . push ( { type : "AddUniqueConstraintOp" , schema : pgSchema , table : uniqueEdge . from . name , column : uniqueEdge . fromField . name , } ) ; } const p2Field1 = prisma2 . findField ( ( m , f ) => edge1 . from . name === m . name && edge1 . to . name === f . name ) ; if ( p2Field1 ) { p2Field1 . setType ( toP2Type ( edge1 . fromField . type ) ) ; } const p2Field2 = prisma2 . findField ( ( m , f ) => edge2 . from . name === m . name && edge2 . to . name === f . name ) ; if ( p2Field2 ) { p2Field2 . setType ( toP2Type ( edge2 . fromField . type ) ) ; } } for ( let [ edge1 , edge2 ] of relations ) { if ( ! isTableHasMany ( edge1 , edge2 ) ) { continue ; } const hasOne = edge1 . type === "hasOne" ? edge1 : edge2 ; const hasMany = edge1 . type === "hasMany" ? edge1 : edge2 ; if ( isOneToMany ( prisma2 , hasOne ) ) { continue ; } const hasManyFieldID = hasMany . from . fields . find ( ( f ) => f . type . named ( ) === "ID" ) ; if ( ! hasManyFieldID ) { continue ; } const hasOneFieldID = hasOne . from . fields . find ( ( f ) => f . type . named ( ) === "ID" ) ; if ( ! hasOneFieldID ) { continue ; } breakingOps . push ( { type : "MigrateHasManyOp" , schema : pgSchema , p1ModelOne : hasOne . from , p1FieldOne : hasOne . fromField , p1FieldOneID : hasOneFieldID , p1ModelMany : hasMany . from , p1FieldManyID : hasManyFieldID , joinTableName : joinTableName ( edge1 . from , edge1 . fromField , edge2 . from , edge2 . fromField ) , } ) ; } for ( let [ edge1 , edge2 ] of relations ) { if ( ! isTableHasOne ( edge1 , edge2 ) ) { continue ; } const hasOne = edge1 ; const hasOneOther = edge2 ; if ( isTableOneToOne ( prisma2 , edge1 , edge2 ) ) { continue ; } const hasOneFieldID = hasOne . from . fields . find ( ( f ) => f . type . named ( ) === "ID" ) ; if ( ! hasOneFieldID ) { continue ; } const hasOneOtherFieldID = hasOneOther . from . fields . find ( ( f ) => f . type . named ( ) === "ID" ) ; if ( ! hasOneOtherFieldID ) { continue ; } if ( ! isTableOneToOne ( prisma2 , hasOne , hasOneOther ) ) { breakingOps . push ( { type : "MigrateOneToOneTableOp" , schema : pgSchema , p1ModelOne : hasOne . from , p1ModelOther : hasOneOther . from , p1FieldOne : hasOne . fromField , p1FieldOneID : hasOneFieldID , p1FieldOtherID : hasOneOtherFieldID , joinTableName : joinTableName ( edge1 . from , edge1 . fromField , edge2 . from , edge2 . fromField ) , } ) ; } const p2Field1 = prisma2 . findField ( ( m , f ) => edge1 . from . name === m . name && edge1 . to . name === f . name ) ; if ( p2Field1 ) { p2Field1 . setType ( toP2Type ( edge1 . fromField . type ) ) ; } const p2Field2 = prisma2 . findField ( ( m , f ) => edge2 . from . name === m . name && edge2 . to . name === f . name ) ; if ( p2Field2 ) { p2Field2 . setType ( toP2Type ( edge2 . fromField . type ) ) ; } } for ( let [ edge1 , edge2 ] of relations ) { if ( ! isInlineRequiredHasMany ( edge1 , edge2 ) ) { continue ; } const hasOne = edge1 . type === "hasOne" ? edge1 : edge2 ; const p2Field = prisma2 . findField ( ( m , f ) => hasOne . from . name === m . name && hasOne . to . name === f . name ) ; if ( ! p2Field || ! p2Field . type . optional ( ) ) { continue ; } breakingOps . push ( { type : "MigrateRequiredHasManyOp" , schema : pgSchema , p1HasOneModel : hasOne . from , p1HasOneField : hasOne . fromField , p1HasManyModel : hasOne . to , p1HasManyField : hasOne . toField , } ) ; } const idOps = [ ] ; let idOp = { type : "AlterIDsOp" , schema : pgSchema , pairs : [ ] , } ; for ( let model of prisma2 . models ) { for ( let field of model . fields ) { const attributeWithID = field . findAttribute ( ( a ) => a . name === "id" ) ; if ( attributeWithID ) { const type = field . type . innermost ( ) . toString ( ) ; if ( type !== "String" ) { continue ; } if ( field . name === "nodeId" ) { continue ; } idOp . pairs . push ( { model , field } ) ; continue ; } const attributeWithRelation = field . findAttribute ( ( a ) => a . name === "relation" ) ; if ( attributeWithRelation ) { const fieldName = getFieldName ( attributeWithRelation ) ; if ( ! fieldName ) { continue ; } let scalar = model . findField ( ( f ) => f . name === fieldName ) ; if ( ! scalar ) { continue ; } const type = scalar . type . innermost ( ) . toString ( ) ; if ( type !== "String" ) { continue ; } idOp . pairs . push ( { model , field : scalar } ) ; continue ; } } } if ( idOp . pairs . length ) { idOps . push ( idOp ) ; } for ( let p1Model of models ) { for ( let p2Model of prisma2 . models ) { if ( p2Model . name !== p1Model . dbname ) { continue ; } for ( let p2Field of p2Model . fields ) { if ( ! p2Field . type . isReference ( ) ) { continue ; } for ( let p1Field of p1Model . fields ) { if ( ! p1Field . type . isReference ( ) ) { continue ; } if ( p2Field . type . innermost ( ) . toString ( ) === p1Field . type . named ( ) ) { for ( let p2Field of p2Model . fields ) { if ( p1Field . name === p2Field . name ) { p2Field . rename ( p2Field . name + "Id" ) ; break ; } } p2Field . setName ( p1Field . name ) ; } } } } } return { schema : prisma2 , warnings , ops , breakingOps , idOps , } ; } const pos = { column : 0 , line : 0 , offset : 0 } ; function ident ( name ) { return { type : "identifier" , name : name , end : pos , start : pos , } ; } function defaultAttr ( value ) { return { type : "attribute" , name : ident ( "default" ) , end : pos , start : pos , arguments : [ { type : "unkeyed_argument" , end : pos , start : pos , value , } , ] , } ; } function updatedAt ( ) { return { type : "attribute" , name : ident ( "updatedAt" ) , end : pos , start : pos , arguments : [ ] , } ; } function defaultNow ( ) { return defaultAttr ( { type : "function_value" , name : { type : "identifier" , name : "now" , start : pos , end : pos , } , start : pos , end : pos , arguments : [ ] , } ) ; } function cuid ( ) { return { type : "function_value" , name : ident ( "cuid" ) , arguments : [ ] , end : pos , start : pos , } ; } function uuid ( ) { return { type : "function_value" , name : ident ( "uuid" ) , arguments : [ ] , end : pos , start : pos , } ; } function hasExpectedDefault ( p1Arg , p2Arg ? ) { if ( ! p2Arg ) return false ; switch ( p1Arg . value . kind ) { case "BooleanValue" : return p2Arg . value . type === "boolean_value" ; case "EnumValue" : return p2Arg . value . type === "reference_value" ; case "FloatValue" : return p2Arg . value . type === "float_value" ; case "IntValue" : return p2Arg . value . type === "int_value" ; case "ListValue" : return p2Arg . value . type === "list_value" ; case "ObjectValue" : return p2Arg . value . type === "map_value" ; case "StringValue" : return p2Arg . value . type === "string_value" ; case "Variable" : return p2Arg . value . type === "function_value" ; case "NullValue" : return false ; } } function hasDefaultNow ( field ) { const attr = field . findAttribute ( ( a ) => a . name === "default" ) ; if ( ! attr ) return false ; return attr . toString ( ) === "@default(now())" ; } function isOneToOne ( schema , edge ) { const fromModel = schema . findModel ( ( m ) => m . name === edge . from . name ) ; if ( ! fromModel ) return false ; const fromField = fromModel . findField ( ( f ) => f . name === edge . fromField . name ) ; if ( ! fromField ) return false ; const uniqueAttr = fromField . findAttribute ( ( a ) => a . name === "unique" ) ; if ( ! uniqueAttr ) return false ; const toModel = schema . findModel ( ( m ) => m . name === edge . to . name ) ; if ( ! toModel ) return false ; const toField = toModel . findField ( ( f ) => f . name === edge . from . name ) ; if ( ! toField ) return false ; if ( ! toField . type . optional ) return false ; return true ; } function isTableOneToOne ( schema , from , to ) { const fromModel = schema . findModel ( ( m ) => m . name === from . from . name ) ; if ( ! fromModel ) { return false ; } const toModel = schema . findModel ( ( m ) => m . name === to . from . name ) ; if ( ! toModel ) { return false ; } const fromField = fromModel . findField ( ( m ) => m . name === toModel . name ) ; if ( ! fromField ) { return false ; } const toField = toModel . findField ( ( m ) => m . name === fromModel . name ) ; if ( ! toField ) { return false ; } if ( fromField . type . list ( ) ) { return false ; } if ( toField . type . list ( ) ) { return false ; } return true ; } function isOneToMany ( schema , hasOneEdge ) { const model = schema . findModel ( ( m ) => m . name === hasOneEdge . from . name ) ; if ( ! model ) { return false ; } const field = model . findField ( ( f ) => f . name === hasOneEdge . fromField . name + "Id" ) ; if ( ! field ) { return false ; } return true ; } function isInlineOneToOne ( edge1 , edge2 ) { return edge1 . type === "hasOne" && edge1 . link !== "TABLE" && edge2 . type === "hasOne" && edge2 . link !== "TABLE" ; } function findUniqueEdge ( edge1 , edge2 ) { if ( edge1 . link === "INLINE" && edge2 . link !== "INLINE" ) { return edge1 ; } if ( edge1 . link !== "INLINE" && edge2 . link === "INLINE" ) { return edge2 ; } return edge1 . from . name > edge2 . from . name ? edge1 : edge2 ; } function isTableHasMany ( edge1 , edge2 ) { const isHasMany = ( edge1 . type === "hasOne" && edge2 . type === "hasMany" ) || ( edge1 . type === "hasMany" && edge2 . type === "hasOne" ) ; const isTable = edge1 . link === "TABLE" || edge2 . link === "TABLE" ; return isHasMany && isTable ; } function isInlineRequiredHasMany ( edge1 , edge2 ) { const hasOne = edge1 . type === "hasOne" ? edge1 : edge2 . type === "hasOne" ? edge2 : null ; if ( ! hasOne ) { return false ; } if ( hasOne . fromField . optional ( ) ) { return false ; } const isHasMany = ( edge1 . type === "hasOne" && edge2 . type === "hasMany" ) || ( edge1 . type === "hasMany" && edge2 . type === "hasOne" ) ; if ( ! isHasMany ) { return false ; } const isInline = edge1 . link !== "TABLE" && edge2 . link !== "TABLE" ; if ( ! isInline ) { return false ; } if ( edge1 . from . name === edge1 . to . name ) { return false ; } return true ; } function isTableHasOne ( edge1 , edge2 ) { const isHasOne = edge1 . type === "hasOne" && edge2 . type === "hasOne" ; const isTable = edge1 . link === "TABLE" || edge2 . link === "TABLE" ; return isHasOne && isTable ; } function isJsonType ( field ) { return field . type . innermost ( ) . toString ( ) === "Json" ; } function isMySQLDefaultText ( provider , field , directive ) { return ( provider === "mysql" && field . type . named ( ) === "String" && directive . name === "default" && ! ! directive . findArgument ( ( arg ) => arg . name === "value" ) ) ; } function isMySQLDefaultJson ( provider , field , directive ) { return ( provider === "mysql" && field . type . named ( ) === "Json" && directive . name === "default" && ! ! directive . findArgument ( ( arg ) => arg . name === "value" ) ) ; } function getDefaultValueString ( attr ) { const arg = attr . findArgument ( ( a ) => a . name === "value" ) ; if ( ! arg || ! arg . value || arg . value . kind !== "StringValue" ) return ; return arg . value . value ; } function getPGSchema ( url ) { const obj = uri . parse ( url , true ) ; if ( obj . query [ "schema" ] ) { return Array . isArray ( obj . query [ "schema" ] ) ? obj . query [ "schema" ] . join ( "" ) : obj . query [ "schema" ] ; } let pathname = obj . pathname || "" ; pathname = pathname . replace ( /^\// , "" ) ; return pathname ? pathname . replace ( /\//g , "$" ) : "default$default" ; } function toP2Type ( dt , optional = true ) { switch ( dt . kind ) { case "ListType" : const listType = { type : "list_type" , end : pos , start : pos , inner : toP2Type ( dt . inner ( ) , true ) , } ; if ( ! optional ) { return listType ; } return { type : "optional_type" , end : pos , start : pos , inner : listType , } ; case "NonNullType" : return toP2Type ( dt . inner ( ) , false ) ; case "NamedType" : const namedType = { type : "named_type" , name : { type : "identifier" , name : dt . name , start : pos , end : pos , } , start : pos , end : pos , } ; if ( ! optional ) { return namedType ; } return { type : "optional_type" , end : pos , start : pos , inner : namedType , } ; } } function joinTableName ( a , af , b , bf ) { const ta = a . name < b . name ? `_${ a . name }To${ b . name }` : `_${ b . name }To${ a . name }` ; const ar = af . findDirective ( ( d ) => d . name === "relation" ) ; const br = bf . findDirective ( ( d ) => d . name === "relation" ) ; if ( ! ar || ! br ) { return ta ; } const an = ar . findArgument ( ( a ) => a . name === "name" ) ; const bn = br . findArgument ( ( a ) => a . name === "name" ) ; if ( ! an || ! bn ) { return ta ; } const av = an . value . kind === "StringValue" ? an . value . value : "" ; const bv = bn . value . kind === "StringValue" ? bn . value . value : "" ; if ( ! av || ! bv || av !== bv ) { return ta ; } return "_" + av ; } function getFieldName ( a ) { const arg = a . arguments . find ( ( a ) => a . key === "fields" ) ; if ( ! arg ) { return ; } const value = arg . value ; if ( value . type !== "list_value" ) { return ; } const inner = value . values [ 0 ] ; if ( ! inner || inner . type !== "reference_value" ) { return ; } return inner . name . name ; }