import { Knex } from "knex" ; import { Column } from "knex-schema-inspector/dist/types/column" ; import db from "../database/connect" ; import { DiffResult , GenerateQueryResult , Operation , SchemaState , Table } from "../types/core" ; import { getDiff , isDirectusTable , logDiff , missingFilter } from "../utils/common" ; import { changedColumnsComparer } from "./comparers" ; const SUPPORTED_DATA_TYPES = [ "integer" , "bigInteger" , "text" , "string" , "float" , "double" , "decimal" , "boolean" , "date" , "dateTime" , "time" , "timestamp" , "binary" , "json" , "jsonb" , "uuid" , ] ; param dbState param fileState export default function createSchemaQueries ( dbState , fileState ) { const dbStateOrdered = sortTables ( dbState ) ; const fileStateOrdered = sortTables ( fileState ) ; const createdTables = dbStateOrdered . filter ( dbTable => ! fileStateOrdered . some ( stateTable => stateTable . name === dbTable . name ) ) ; const deletedTables = fileStateOrdered . filter ( stateTable => ! dbStateOrdered . some ( dbTable => dbTable . name === stateTable . name ) ) ; const dbStateColumns = getChangedColumns ( dbStateOrdered , createdTables , deletedTables ) ; const fileStateColumns = getChangedColumns ( fileStateOrdered , createdTables , deletedTables ) ; const changedColumns = getDiff ( dbStateColumns , fileStateColumns , changedColumnsComparer ) ; logDiff ( { tables : createdTables } , "create" ) ; logDiff ( { tables : deletedTables } , "delete" ) ; logDiff ( { tables : changedColumns } , "update" ) ; const changedQueries = generateChangedColumnsQueries ( dbStateColumns , fileStateColumns , changedColumns ) ; return { up : [ ... createdTables . map ( createTable ) , ... deletedTables . map ( dropTable ) , ... changedQueries . up ] , down : [ ... createdTables . reverse ( ) . map ( dropTable ) , ... deletedTables . reverse ( ) . map ( createTable ) , ... changedQueries . down , ] , } ; } param tables param createdTables param deletedTables const getChangedColumns = ( tables , createdTables , deletedTables ) => { return tables . filter ( table => ! createdTables . some ( created => created . name === table . name ) && ! deletedTables . some ( deleted => deleted . name === table . name ) ) . flatMap ( table => table . columns ) ; } ; const createTable = ( table ) : string => db ( ) . schema . createTable ( table . name , tableBuilder => table . columns . forEach ( column => buildColumn ( tableBuilder , column ) ) ) . toString ( ) ; const dropTable = ( table ) => db ( ) . schema . dropTable ( table . name ) . toString ( ) ; param tables const sortTables = ( tables ) : Table [ ] => { const tableNames = tables . map ( table => table . name ) ; const ordered = [ ] ; while ( tables . length !== ordered . length ) { const orderedNames = ordered . map ( table => table . name ) ; tables . forEach ( table => { if ( orderedNames . includes ( table . name ) ) { return ; } const dependentTables = table . columns . filter ( column => column . foreign_key_table && column . foreign_key_table !== table . name && ! isDirectusTable ( column . foreign_key_table ) && tableNames . includes ( column . foreign_key_table ) ) . map ( column => column . foreign_key_table ) ; if ( dependentTables . some ( table => ! orderedNames . includes ( table ) ) ) { return ; } ordered . push ( table ) ; } ) ; } return ordered ; } ; const createColumnBuilder = ( tableBuilder , { name , data_type , has_auto_increment , max_length , numeric_precision , numeric_scale } ) : Knex . ColumnBuilder => { if ( has_auto_increment ) { return data_type === "integer" ? tableBuilder . increments ( name ) : tableBuilder . bigIncrements ( name ) ; } if ( [ "string" , "binary" ] . includes ( data_type ) ) { return tableBuilder [ data_type ] ( name , max_length || undefined ) ; } if ( [ "float" , "double" , "decimal" ] . includes ( data_type ) ) { return tableBuilder [ data_type ] ( name , numeric_precision || undefined , numeric_scale || undefined ) ; } /**
     * The rest of the types do not have specific additional arguments, so they are merged here.
     * We check first if the data_type is any of the 1st party knex-supported and use it.
     * Otherwise we default to a specific type to ensure broader compatibility with dialect-specific types.
     */ return SUPPORTED_DATA_TYPES . map ( type => type . toString ( ) ) . includes ( data_type ) ? tableBuilder [ data_type ] ( name ) : tableBuilder . specificType ( name , data_type ) ; } ; const buildColumn = ( tableBuilder , column ) : Knex . ColumnBuilder => { const columnBuilder = createColumnBuilder ( tableBuilder , column ) ; if ( ! column . has_auto_increment ) { columnBuilder . defaultTo ( column . default_value ) ; } if ( column . is_nullable ) { columnBuilder . nullable ( ) ; } if ( column . is_unique ) { columnBuilder . unique ( ) ; } if ( column . is_primary_key ) { columnBuilder . primary ( ) ; } if ( column . comment ) { columnBuilder . comment ( column . comment ) ; } if ( column . foreign_key_column ) { columnBuilder . references ( column . foreign_key_column ) . inTable ( column . foreign_key_table ) ; } return columnBuilder ; } ; param dbStateColumns param fileStateColumns param updatedColumns const generateChangedColumnsQueries = ( dbStateColumns , fileStateColumns , updatedColumns ) : GenerateQueryResult => { const deletedColumns = fileStateColumns . filter ( missingFilter ( dbStateColumns , changedColumnsComparer ) ) ; const createdColumns = dbStateColumns . filter ( missingFilter ( fileStateColumns , changedColumnsComparer ) ) ; const deleteColumnQuery = ( column ) => db ( ) . schema . alterTable ( column . table , tableBuilder => { tableBuilder . dropColumns ( column . name ) ; } ) . toString ( ) ; const buildColumnQuery = ( operation ) => ( column ) => { return db ( ) . schema . alterTable ( column . table , tableBuilder => { const columnBuilder = buildColumn ( tableBuilder , column ) ; if ( operation === "update" ) { columnBuilder . alter ( ) ; } } ) . toString ( ) ; } ; return { up : [ ... createdColumns . map ( buildColumnQuery ( "create" ) ) , ... deletedColumns . map ( deleteColumnQuery ) , ... updatedColumns . map ( ( { newItem } ) => buildColumnQuery ( "update" ) ( newItem ) ) , ] , down : [ ... createdColumns . map ( deleteColumnQuery ) , ... deletedColumns . map ( buildColumnQuery ( "create" ) ) , ... updatedColumns . map ( ( { oldItem } ) => buildColumnQuery ( "update" ) ( oldItem ) ) , ] , } ; } ;