diff --git a/drizzle-seed/package.json b/drizzle-seed/package.json index 57ea7fa86c..d0bd73951b 100644 --- a/drizzle-seed/package.json +++ b/drizzle-seed/package.json @@ -11,11 +11,13 @@ "generate-for-tests:pg": "drizzle-kit generate --config=./src/tests/pg/drizzle.config.ts", "generate-for-tests:mysql": "drizzle-kit generate --config=./src/tests/mysql/drizzle.config.ts", "generate-for-tests:sqlite": "drizzle-kit generate --config=./src/tests/sqlite/drizzle.config.ts", + "generate-for-tests:singlestore": "drizzle-kit generate --config=./src/tests/singlestore/drizzle.config.ts", "generate": "drizzle-kit generate", "start": "npx tsx ./src/dev/test.ts", "start:pg": "npx tsx ./src/tests/northwind/pgTest.ts", "start:mysql": "npx tsx ./src/tests/northwind/mysqlTest.ts", "start:sqlite": "npx tsx ./src/tests/northwind/sqliteTest.ts", + "start:singlestore": "npx tsx ./tests/northwind/singlestoreTest.ts", "benchmark": "npx tsx ./src/tests/benchmarks/generatorsBenchmark.ts", "publish": "npm publish package.tgz" }, diff --git a/drizzle-seed/src/index.ts b/drizzle-seed/src/index.ts index a56134ac32..e229ff9f0f 100644 --- a/drizzle-seed/src/index.ts +++ b/drizzle-seed/src/index.ts @@ -15,6 +15,9 @@ import { getTableConfig as getMysqlTableConfig, MySqlDatabase, MySqlTable } from import type { PgArray, PgColumn, PgSchema } from 'drizzle-orm/pg-core'; import { getTableConfig as getPgTableConfig, PgDatabase, PgTable } from 'drizzle-orm/pg-core'; +import type { SingleStoreColumn, SingleStoreSchema } from 'drizzle-orm/singlestore-core'; +import { getTableConfig as getSingleStoreTableConfig, SingleStoreDatabase, SingleStoreTable } from 'drizzle-orm/singlestore-core'; + import type { SQLiteColumn } from 'drizzle-orm/sqlite-core'; import { BaseSQLiteDatabase, getTableConfig as getSqliteTableConfig, SQLiteTable } from 'drizzle-orm/sqlite-core'; @@ -29,9 +32,10 @@ type InferCallbackType< DB extends | PgDatabase | MySqlDatabase + | SingleStoreDatabase | BaseSQLiteDatabase, SCHEMA extends { - [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SQLiteTable | Relations; + [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SingleStoreTable | SingleStoreSchema | SQLiteTable | Relations; }, > = DB extends PgDatabase ? SCHEMA extends { [key: string]: @@ -39,6 +43,8 @@ type InferCallbackType< | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | Relations; } ? { @@ -99,12 +105,49 @@ type InferCallbackType< }; } : {} + : DB extends SingleStoreDatabase ? SCHEMA extends { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SingleStoreTable + | SingleStoreSchema + | SQLiteTable + | Relations; + } ? { + // iterates through schema fields. example -> schema: {"tableName": SingleStoreTable} + [ + table in keyof SCHEMA as SCHEMA[table] extends SingleStoreTable ? table + : never + ]?: { + count?: number; + columns?: { + // iterates through table fields. example -> table: {"columnName": SingleStoreColumn} + [ + column in keyof SCHEMA[table] as SCHEMA[table][column] extends SingleStoreColumn ? column + : never + ]?: AbstractGenerator; + }; + with?: { + [ + refTable in keyof SCHEMA as SCHEMA[refTable] extends SingleStoreTable ? refTable + : never + ]?: + | number + | { weight: number; count: number | number[] }[]; + }; + }; + } + : {} : DB extends BaseSQLiteDatabase ? SCHEMA extends { [key: string]: | PgTable | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | Relations; } ? { @@ -138,9 +181,10 @@ class SeedPromise< DB extends | PgDatabase | MySqlDatabase + | SingleStoreDatabase | BaseSQLiteDatabase, SCHEMA extends { - [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SQLiteTable | Relations; + [key: string]: PgTable | PgSchema | MySqlTable | MySqlSchema | SingleStoreTable | SingleStoreSchema | SQLiteTable | Relations; }, VERSION extends string | undefined, > implements Promise { @@ -347,6 +391,7 @@ export function seed< DB extends | PgDatabase | MySqlDatabase + | SingleStoreDatabase | BaseSQLiteDatabase, SCHEMA extends { [key: string]: @@ -354,6 +399,8 @@ export function seed< | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | Relations | any; @@ -364,13 +411,15 @@ export function seed< } const seedFunc = async ( - db: PgDatabase | MySqlDatabase | BaseSQLiteDatabase, + db: PgDatabase | MySqlDatabase | SingleStoreDatabase | BaseSQLiteDatabase, schema: { [key: string]: | PgTable | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | Relations | any; @@ -387,11 +436,13 @@ const seedFunc = async ( await seedPostgres(db, schema, { ...options, version }, refinements); } else if (is(db, MySqlDatabase)) { await seedMySql(db, schema, { ...options, version }, refinements); + } else if (is(db, SingleStoreDatabase)) { + await seedSingleStore(db, schema, { ...options, version }, refinements); } else if (is(db, BaseSQLiteDatabase)) { await seedSqlite(db, schema, { ...options, version }, refinements); } else { throw new Error( - 'The drizzle-seed package currently supports only PostgreSQL, MySQL, and SQLite databases. Please ensure your database is one of these supported types', + 'The drizzle-seed package currently supports only PostgreSQL, MySQL, SingleStore, and SQLite databases. Please ensure your database is one of these supported types', ); } @@ -442,6 +493,7 @@ export async function reset< DB extends | PgDatabase | MySqlDatabase + | SingleStoreDatabase | BaseSQLiteDatabase, SCHEMA extends { [key: string]: @@ -449,6 +501,8 @@ export async function reset< | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | any; }, @@ -465,6 +519,12 @@ export async function reset< if (Object.entries(mysqlTables).length > 0) { await resetMySql(db, mysqlTables); } + } else if (is(db, SingleStoreDatabase)) { + const { singlestoreTables } = filterSingleStoreTables(schema); + + if (Object.entries(singlestoreTables).length > 0) { + await resetSingleStore(db, singlestoreTables); + } } else if (is(db, BaseSQLiteDatabase)) { const { sqliteTables } = filterSqliteTables(schema); @@ -473,7 +533,7 @@ export async function reset< } } else { throw new Error( - 'The drizzle-seed package currently supports only PostgreSQL, MySQL, and SQLite databases. Please ensure your database is one of these supported types', + 'The drizzle-seed package currently supports only PostgreSQL, MySQL, SingleStore, and SQLite databases. Please ensure your database is one of these supported types', ); } } @@ -499,6 +559,8 @@ const filterPgSchema = (schema: { | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | Relations | any; @@ -524,6 +586,8 @@ const seedPostgres = async ( | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | Relations | any; @@ -889,6 +953,8 @@ const filterMysqlTables = (schema: { | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | any; }) => { @@ -916,6 +982,8 @@ const seedMySql = async ( | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | Relations | any; @@ -1210,6 +1278,8 @@ const filterSqliteTables = (schema: { | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | any; }) => { @@ -1237,6 +1307,8 @@ const seedSqlite = async ( | PgSchema | MySqlTable | MySqlSchema + | SingleStoreTable + | SingleStoreSchema | SQLiteTable | Relations | any; @@ -1282,6 +1354,316 @@ const seedSqlite = async ( ); }; +// SingleStore------------------------------------------------------------------------------------------------------------------------- +const resetSingleStore = async ( + db: SingleStoreDatabase, + schema: { [key: string]: SingleStoreTable }, +) => { + const tablesToTruncate = Object.entries(schema).map(([_tsTableName, table]) => { + const dbTableName = getTableName(table); + return dbTableName; + }); + + await db.execute(sql.raw('SET FOREIGN_KEY_CHECKS = 0;')); + + for (const tableName of tablesToTruncate) { + const sqlQuery = `truncate \`${tableName}\`;`; + await db.execute(sql.raw(sqlQuery)); + } + + await db.execute(sql.raw('SET FOREIGN_KEY_CHECKS = 1;')); +}; + +const filterSingleStoreTables = (schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SingleStoreTable + | SingleStoreSchema + | SQLiteTable + | any; +}) => { + const singlestoreSchema = Object.fromEntries( + Object.entries(schema).filter( + (keyValue): keyValue is [string, SingleStoreTable | Relations] => + is(keyValue[1], SingleStoreTable) || is(keyValue[1], Relations), + ), + ); + + const singlestoreTables = Object.fromEntries( + Object.entries(schema).filter( + (keyValue): keyValue is [string, SingleStoreTable] => is(keyValue[1], SingleStoreTable), + ), + ); + + return { singlestoreSchema, singlestoreTables }; +}; + +const seedSingleStore = async ( + db: SingleStoreDatabase, + schema: { + [key: string]: + | PgTable + | PgSchema + | MySqlTable + | MySqlSchema + | SingleStoreTable + | SingleStoreSchema + | SQLiteTable + | Relations + | any; + }, + options: { count?: number; seed?: number; version?: number } = {}, + refinements?: RefinementsType, +) => { + const { singlestoreSchema, singlestoreTables } = filterSingleStoreTables(schema); + const { tables, relations } = getSingleStoreInfo(singlestoreSchema, singlestoreTables); + + const seedService = new SeedService(); + + const generatedTablesGenerators = seedService.generatePossibleGenerators( + 'singlestore', + tables, + relations, + refinements, + options, + ); + + const preserveCyclicTablesData = relations.some((rel) => rel.isCyclic === true); + + const tablesValues = await seedService.generateTablesValues( + relations, + generatedTablesGenerators, + db, + singlestoreTables, + { ...options, preserveCyclicTablesData }, + ); + + const { filteredTablesGenerators, tablesUniqueNotNullColumn } = seedService.filterCyclicTables( + generatedTablesGenerators, + ); + const updateDataInDb = filteredTablesGenerators.length === 0 ? false : true; + + await seedService.generateTablesValues( + relations, + filteredTablesGenerators, + db, + singlestoreTables, + { ...options, tablesValues, updateDataInDb, tablesUniqueNotNullColumn }, + ); +}; + +const getSingleStoreInfo = ( + singlestoreSchema: { [key: string]: SingleStoreTable | Relations }, + singlestoreTables: { [key: string]: SingleStoreTable }, +) => { + let tableConfig: ReturnType; + let dbToTsColumnNamesMap: { [key: string]: string }; + + const dbToTsTableNamesMap: { [key: string]: string } = Object.fromEntries( + Object.entries(singlestoreTables).map(([key, value]) => [getTableName(value), key]), + ); + + const tables: Table[] = []; + const relations: RelationWithReferences[] = []; + const dbToTsColumnNamesMapGlobal: { + [tableName: string]: { [dbColumnName: string]: string }; + } = {}; + const tableRelations: { [tableName: string]: RelationWithReferences[] } = {}; + + const getDbToTsColumnNamesMap = (table: SingleStoreTable) => { + let dbToTsColumnNamesMap: { [dbColName: string]: string } = {}; + + const tableName = getTableName(table); + if (Object.hasOwn(dbToTsColumnNamesMapGlobal, tableName)) { + dbToTsColumnNamesMap = dbToTsColumnNamesMapGlobal[tableName]!; + return dbToTsColumnNamesMap; + } + + const tableConfig = getSingleStoreTableConfig(table); + for (const [tsCol, col] of Object.entries(tableConfig.columns[0]!.table)) { + dbToTsColumnNamesMap[col.name] = tsCol; + } + dbToTsColumnNamesMapGlobal[tableName] = dbToTsColumnNamesMap; + + return dbToTsColumnNamesMap; + }; + + const transformFromDrizzleRelation = ( + schema: Record, + getDbToTsColumnNamesMap: (table: SingleStoreTable) => { + [dbColName: string]: string; + }, + tableRelations: { + [tableName: string]: RelationWithReferences[]; + }, + ) => { + const schemaConfig = extractTablesRelationalConfig(schema, createTableRelationsHelpers); + const relations: RelationWithReferences[] = []; + for (const table of Object.values(schemaConfig.tables)) { + if (table.relations === undefined) continue; + + for (const drizzleRel of Object.values(table.relations)) { + if (!is(drizzleRel, One)) continue; + + const tableConfig = getSingleStoreTableConfig(drizzleRel.sourceTable as SingleStoreTable); + const tableDbSchema = tableConfig.schema ?? 'public'; + const tableDbName = tableConfig.name; + const tableTsName = schemaConfig.tableNamesMap[`${tableDbSchema}.${tableDbName}`] ?? tableDbName; + + const dbToTsColumnNamesMap = getDbToTsColumnNamesMap(drizzleRel.sourceTable as SingleStoreTable); + const columns = drizzleRel.config?.fields.map((field) => dbToTsColumnNamesMap[field.name] as string) + ?? []; + + const refTableConfig = getSingleStoreTableConfig(drizzleRel.referencedTable as SingleStoreTable); + const refTableDbSchema = refTableConfig.schema ?? 'public'; + const refTableDbName = refTableConfig.name; + const refTableTsName = schemaConfig.tableNamesMap[`${refTableDbSchema}.${refTableDbName}`] + ?? refTableDbName; + + const dbToTsColumnNamesMapForRefTable = getDbToTsColumnNamesMap(drizzleRel.referencedTable as SingleStoreTable); + const refColumns = drizzleRel.config?.references.map((ref) => + dbToTsColumnNamesMapForRefTable[ref.name] as string + ) + ?? []; + + if (tableRelations[refTableTsName] === undefined) { + tableRelations[refTableTsName] = []; + } + + const relation: RelationWithReferences = { + table: tableTsName, + columns, + refTable: refTableTsName, + refColumns, + refTableRels: tableRelations[refTableTsName], + type: 'one', + }; + + // do not add duplicate relation + if ( + tableRelations[tableTsName]?.some((rel) => + rel.table === relation.table + && rel.refTable === relation.refTable + ) + ) { + console.warn( + `You are providing a one-to-many relation between the '${relation.refTable}' and '${relation.table}' tables,\n` + + `while the '${relation.table}' table object already has foreign key constraint in the schema referencing '${relation.refTable}' table.\n` + + `In this case, the foreign key constraint will be used.\n`, + ); + continue; + } + + relations.push(relation); + tableRelations[tableTsName]!.push(relation); + } + } + return relations; + }; + + for (const table of Object.values(singlestoreTables)) { + tableConfig = getSingleStoreTableConfig(table); + + dbToTsColumnNamesMap = {}; + for (const [tsCol, col] of Object.entries(tableConfig.columns[0]!.table)) { + dbToTsColumnNamesMap[col.name] = tsCol; + } + + // SingleStore doesn't support foreign keys + const newRelations: RelationWithReferences[] = []; + relations.push(...newRelations); + + if (tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] === undefined) { + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string] = []; + } + tableRelations[dbToTsTableNamesMap[tableConfig.name] as string]!.push(...newRelations); + + const getTypeParams = (sqlType: string) => { + // get type params and set only type + const typeParams: Column['typeParams'] = {}; + + // Handle vector column dimensions + if (sqlType.startsWith('vector')) { + const match = sqlType.match(/\((\d+)\)/); + if (match) { + typeParams['dimensions'] = Number(match[1]); + } + } else if ( + sqlType.startsWith('decimal') + || sqlType.startsWith('real') + || sqlType.startsWith('double') + || sqlType.startsWith('float') + ) { + const match = sqlType.match(/\((\d+), *(\d+)\)/); + if (match) { + typeParams['precision'] = Number(match[1]); + typeParams['scale'] = Number(match[2]); + } + } else if ( + sqlType.startsWith('char') + || sqlType.startsWith('varchar') + || sqlType.startsWith('binary') + || sqlType.startsWith('varbinary') + ) { + const match = sqlType.match(/\((\d+)\)/); + if (match) { + typeParams['length'] = Number(match[1]); + } + } + + return typeParams; + }; + + tables.push({ + name: dbToTsTableNamesMap[tableConfig.name] as string, + columns: tableConfig.columns.map((column) => ({ + name: dbToTsColumnNamesMap[column.name] as string, + columnType: column.getSQLType(), + typeParams: getTypeParams(column.getSQLType()), + dataType: column.dataType, + hasDefault: column.hasDefault, + default: column.default, + enumValues: column.enumValues, + isUnique: column.isUnique, + notNull: column.notNull, + primary: column.primary, + })), + primaryKeys: tableConfig.columns + .filter((column) => column.primary) + .map((column) => dbToTsColumnNamesMap[column.name] as string), + }); + } + + const transformedDrizzleRelations = transformFromDrizzleRelation( + singlestoreSchema, + getDbToTsColumnNamesMap, + tableRelations, + ); + relations.push( + ...transformedDrizzleRelations, + ); + + const isCyclicRelations = relations.map( + (relI) => { + const tableRel = tableRelations[relI.table]?.find((relJ) => relJ.refTable === relI.refTable); + if (tableRel && isRelationCyclic(relI)) { + tableRel['isCyclic'] = true; + return { ...relI, isCyclic: true }; + } + if (tableRel) { + tableRel['isCyclic'] = false; + } + return { ...relI, isCyclic: false }; + }, + ); + + return { tables, relations: isCyclicRelations, tableRelations }; +}; + +// Sqlite------------------------------------------------------------------------------------------------------------------------ const getSqliteInfo = ( sqliteSchema: { [key: string]: SQLiteTable | Relations }, sqliteTables: { [key: string]: SQLiteTable }, diff --git a/drizzle-seed/src/services/SeedService.ts b/drizzle-seed/src/services/SeedService.ts index 22d92655cd..bb71d29693 100644 --- a/drizzle-seed/src/services/SeedService.ts +++ b/drizzle-seed/src/services/SeedService.ts @@ -4,6 +4,8 @@ import type { MySqlTable, MySqlTableWithColumns } from 'drizzle-orm/mysql-core'; import { MySqlDatabase } from 'drizzle-orm/mysql-core'; import type { PgTable, PgTableWithColumns } from 'drizzle-orm/pg-core'; import { PgDatabase } from 'drizzle-orm/pg-core'; +import type { SingleStoreTable, SingleStoreTableWithColumns } from 'drizzle-orm/singlestore-core'; +import { SingleStoreDatabase } from 'drizzle-orm/singlestore-core'; import type { SQLiteTable, SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core'; import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'; import type { @@ -27,12 +29,14 @@ export class SeedService { private postgresMaxParametersNumber = 65535; // there is no max parameters number in mysql, so you can increase mysqlMaxParametersNumber if it's needed. private mysqlMaxParametersNumber = 100000; + // SingleStore is MySQL-compatible, so use similar parameter limits + private singlestoreMaxParametersNumber = 100000; // SQLITE_MAX_VARIABLE_NUMBER, which by default equals to 999 for SQLite versions prior to 3.32.0 (2020-05-22) or 32766 for SQLite versions after 3.32.0. private sqliteMaxParametersNumber = 32766; private version?: number; generatePossibleGenerators = ( - connectionType: 'postgresql' | 'mysql' | 'sqlite', + connectionType: 'postgresql' | 'mysql' | 'sqlite' | 'singlestore', tables: Table[], relations: (Relation & { isCyclic: boolean })[], refinements?: RefinementsType, @@ -261,6 +265,11 @@ export class SeedService { table, col, ); + } else if (connectionType === 'singlestore') { + columnPossibleGenerator.generator = this.selectGeneratorForSingleStoreColumn( + table, + col, + ); } else if (connectionType === 'sqlite') { columnPossibleGenerator.generator = this.selectGeneratorForSqlite( table, @@ -990,6 +999,194 @@ export class SeedService { return generator; }; + selectGeneratorForSingleStoreColumn = ( + table: Table, + col: Column, + ) => { + const pickGenerator = (table: Table, col: Column) => { + // VECTOR - SingleStore-specific type + if (col.columnType.startsWith('vector')) { + const dimensions = col.typeParams.dimensions || 1536; + const generator = new generatorsMap.GenerateArray[0]({ + baseColumnGen: new generatorsMap.GenerateNumber[0]({ + minValue: -1, + maxValue: 1, + precision: 0.0001 + }), + size: dimensions + }); + return generator; + } + + // INT - Primary key handling + if ( + (col.columnType.includes('bigint') || col.columnType.includes('int')) + && table.primaryKeys.includes(col.name) + ) { + const generator = new generatorsMap.GenerateIntPrimaryKey[0](); + return generator; + } + + // INT - Regular integer columns + let minValue: number | bigint | undefined; + let maxValue: number | bigint | undefined; + + if (col.columnType.includes('int')) { + if (col.columnType === 'tinyint') { + minValue = -128; + maxValue = 127; + } else if (col.columnType === 'smallint') { + minValue = -32768; + maxValue = 32767; + } else if (col.columnType === 'mediumint') { + minValue = -8388608; + maxValue = 8388607; + } else if (col.columnType === 'int') { + minValue = -2147483648; + maxValue = 2147483647; + } else if (col.columnType === 'bigint') { + minValue = BigInt('-9223372036854775808'); + maxValue = BigInt('9223372036854775807'); + } + + const generator = new generatorsMap.GenerateInt[0]({ + minValue, + maxValue, + }); + return generator; + } + + // NUMBER (decimal, float, double, real) + if ( + col.columnType.startsWith('decimal') || + col.columnType.startsWith('float') || + col.columnType.startsWith('double') || + col.columnType.startsWith('real') || + col.columnType.startsWith('numeric') + ) { + if (col.typeParams.precision !== undefined) { + const precision = col.typeParams.precision; + const scale = col.typeParams.scale === undefined ? 0 : col.typeParams.scale; + const maxAbsoluteValue = Math.pow(10, precision - scale) - Math.pow(10, -scale); + + const generator = new generatorsMap.GenerateNumber[0]({ + minValue: -maxAbsoluteValue, + maxValue: maxAbsoluteValue, + precision: Math.pow(10, scale), + }); + return generator; + } + + const generator = new generatorsMap.GenerateNumber[0](); + return generator; + } + + // STRING - Primary key handling + if ( + (col.columnType === 'text' + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('char')) + && table.primaryKeys.includes(col.name) + ) { + const generator = new generatorsMap.GenerateUniqueString[0](); + return generator; + } + + // STRING - Name fields + if ( + (col.columnType === 'text' + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('char')) + && col.name.toLowerCase().includes('name') + ) { + const generator = new generatorsMap.GenerateFirstName[0](); + return generator; + } + + // STRING - Email fields + if ( + (col.columnType === 'text' + || col.columnType.startsWith('varchar') + || col.columnType.startsWith('char')) + && col.name.toLowerCase().includes('email') + ) { + const generator = new generatorsMap.GenerateEmail[0](); + return generator; + } + + // STRING - General text fields + if ( + col.columnType === 'text' || + col.columnType.startsWith('varchar') || + col.columnType.startsWith('char') + ) { + const generator = new generatorsMap.GenerateString[0](); + return generator; + } + + // BOOLEAN + if (col.columnType === 'boolean') { + const generator = new generatorsMap.GenerateBoolean[0](); + return generator; + } + + // DATE, TIME, TIMESTAMP, DATETIME + if (col.columnType.includes('datetime')) { + const generator = new generatorsMap.GenerateDatetime[0](); + return generator; + } + + if (col.columnType.includes('date')) { + const generator = new generatorsMap.GenerateDate[0](); + return generator; + } + + if (col.columnType === 'time') { + const generator = new generatorsMap.GenerateTime[0](); + return generator; + } + + if (col.columnType.includes('timestamp')) { + const generator = new generatorsMap.GenerateTimestamp[0](); + return generator; + } + + // JSON + if (col.columnType === 'json') { + const generator = new generatorsMap.GenerateJson[0](); + return generator; + } + + // ENUM + if (col.enumValues !== undefined) { + const generator = new generatorsMap.GenerateEnum[0]({ + enumValues: col.enumValues, + }); + return generator; + } + + // DEFAULT values + if (col.hasDefault && col.default !== undefined) { + const generator = new generatorsMap.GenerateDefault[0]({ + defaultValue: col.default, + }); + return generator; + } + + // Return undefined for unsupported types + return; + }; + + const generator = pickGenerator(table, col); + if (generator !== undefined) { + generator.isUnique = col.isUnique; + generator.dataType = col.dataType; + generator.stringLength = col.typeParams.length; + } + + return generator; + }; + selectGeneratorForSqlite = ( table: Table, col: Column, @@ -1145,8 +1342,9 @@ export class SeedService { db?: | PgDatabase | MySqlDatabase + | SingleStoreDatabase | BaseSQLiteDatabase, - schema?: { [key: string]: PgTable | MySqlTable | SQLiteTable }, + schema?: { [key: string]: PgTable | MySqlTable | SingleStoreTable | SQLiteTable }, options?: { count?: number; seed?: number; @@ -1367,8 +1565,9 @@ export class SeedService { db?: | PgDatabase | MySqlDatabase + | SingleStoreDatabase | BaseSQLiteDatabase; - schema?: { [key: string]: PgTable | MySqlTable | SQLiteTable }; + schema?: { [key: string]: PgTable | MySqlTable | SingleStoreTable | SQLiteTable }; tableName?: string; count?: number; preserveData?: boolean; @@ -1422,6 +1621,8 @@ export class SeedService { : this.postgresMaxParametersNumber; } else if (is(db, MySqlDatabase)) { maxParametersNumber = this.mysqlMaxParametersNumber; + } else if (is(db, SingleStoreDatabase)) { + maxParametersNumber = this.singlestoreMaxParametersNumber; } else { // is(db, BaseSQLiteDatabase) maxParametersNumber = this.sqliteMaxParametersNumber; @@ -1546,9 +1747,10 @@ export class SeedService { db: | PgDatabase | MySqlDatabase + | SingleStoreDatabase | BaseSQLiteDatabase; schema: { - [key: string]: PgTable | MySqlTable | SQLiteTable; + [key: string]: PgTable | MySqlTable | SingleStoreTable | SQLiteTable; }; tableName: string; override: boolean; @@ -1563,6 +1765,10 @@ export class SeedService { await db .insert((schema as { [key: string]: MySqlTable })[tableName]!) .values(generatedValues); + } else if (is(db, SingleStoreDatabase)) { + await db + .insert((schema as { [key: string]: SingleStoreTable })[tableName]!) + .values(generatedValues); } else if (is(db, BaseSQLiteDatabase)) { await db .insert((schema as { [key: string]: SQLiteTable })[tableName]!) @@ -1583,9 +1789,10 @@ export class SeedService { db: | PgDatabase | MySqlDatabase + | SingleStoreDatabase | BaseSQLiteDatabase; schema: { - [key: string]: PgTable | MySqlTable | SQLiteTable; + [key: string]: PgTable | MySqlTable | SingleStoreTable | SQLiteTable; }; tableName: string; uniqueNotNullColName: string; @@ -1601,6 +1808,11 @@ export class SeedService { await db.update(table).set(generatedValues[0]!).where( eq(table[uniqueNotNullColName], generatedValues[0]![uniqueNotNullColName]), ); + } else if (is(db, SingleStoreDatabase)) { + const table = (schema as { [key: string]: SingleStoreTableWithColumns })[tableName]!; + await db.update(table).set(generatedValues[0]!).where( + eq(table[uniqueNotNullColName], generatedValues[0]![uniqueNotNullColName]), + ); } else if (is(db, BaseSQLiteDatabase)) { const table = (schema as { [key: string]: SQLiteTableWithColumns })[tableName]!; await db.update(table).set(generatedValues[0]!).where( diff --git a/drizzle-seed/tests/northwind/singlestoreSchema.ts b/drizzle-seed/tests/northwind/singlestoreSchema.ts new file mode 100644 index 0000000000..3352f4b8fa --- /dev/null +++ b/drizzle-seed/tests/northwind/singlestoreSchema.ts @@ -0,0 +1,110 @@ +// Import singlestore-core modules for schema definition +import { + float, + int, + singlestoreTable, + text, + timestamp, + varchar, + vector +} from 'drizzle-orm/singlestore-core'; + +export const customers = singlestoreTable('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = singlestoreTable( + 'employee', + { + id: int('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: int('extension').notNull(), + notes: text('notes').notNull(), + // SingleStore doesn't support self-referencing foreign keys + reportsTo: int('reports_to'), + photoPath: text('photo_path'), + }, +); + +export const orders = singlestoreTable('order', { + id: int('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: int('ship_via').notNull(), + freight: float('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + // SingleStore doesn't support foreign key constraints + customerId: varchar('customer_id', { length: 256 }).notNull(), + employeeId: int('employee_id').notNull(), +}); + +export const suppliers = singlestoreTable('supplier', { + id: int('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = singlestoreTable('product', { + id: int('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: float('unit_price').notNull(), + unitsInStock: int('units_in_stock').notNull(), + unitsOnOrder: int('units_on_order').notNull(), + reorderLevel: int('reorder_level').notNull(), + discontinued: int('discontinued').notNull(), + + // SingleStore doesn't support foreign key constraints + supplierId: int('supplier_id').notNull(), +}); + +export const details = singlestoreTable('order_detail', { + unitPrice: float('unit_price').notNull(), + quantity: int('quantity').notNull(), + discount: float('discount').notNull(), + + // SingleStore doesn't support foreign key constraints + orderId: int('order_id').notNull(), + productId: int('product_id').notNull(), +}); + +// SingleStore-specific table with vector support +export const documentEmbeddings = singlestoreTable('document_embedding', { + id: int('id').primaryKey(), + title: text('title').notNull(), + content: text('content').notNull(), + embedding: vector('embedding', { dimensions: 1536 }), +}); diff --git a/drizzle-seed/tests/northwind/singlestoreTest.ts b/drizzle-seed/tests/northwind/singlestoreTest.ts new file mode 100644 index 0000000000..f866a39495 --- /dev/null +++ b/drizzle-seed/tests/northwind/singlestoreTest.ts @@ -0,0 +1,353 @@ +import 'dotenv/config'; +import { drizzle } from 'drizzle-orm/singlestore'; +import * as mysql2 from 'mysql2/promise'; + +import * as schema from './singlestoreSchema.ts'; +import { seed } from '../../src/index.ts'; + +// Function to get a SingleStore client +const getSingleStoreClient = async () => { + const connectionString = process.env['SINGLESTORE_CONNECTION_STRING'] ?? + 'singlestore://root:singlestore@localhost:3306/drizzle'; + + console.log('Connecting to SingleStore with connection string:', connectionString); + + try { + const client = await mysql2.createConnection({ + uri: connectionString, + supportBigNumbers: true, + // Set shorter timeout for local dev + connectTimeout: 5000, + }); + + await client.query(`CREATE DATABASE IF NOT EXISTS drizzle;`); + await client.changeUser({ database: 'drizzle' }); + + return client; + } catch (error) { + console.error('Error connecting to SingleStore:', error); + throw error; + } +} + +// IIFE to execute the main code +(async () => { + try { + // Connect to SingleStore + console.log('Attempting to connect to SingleStore...'); + const client = await getSingleStoreClient(); + const db = drizzle(client); + + console.log('SingleStore database connection was established successfully.'); + + // Create schema for Northwind demo + await createSingleStoreSchema(db); + console.log('SingleStore tables were created.'); + + // For serious migrations, use the migrator + // await migrate(db, { migrationsFolder: path.join(__dirname, '../../../singlestoreMigrations') }); + // console.log('SingleStore database was migrated.'); + + // Define seed data parameters + const titlesOfCourtesy = ['Ms.', 'Mrs.', 'Dr.']; + const unitsOnOrders = [0, 10, 20, 30, 50, 60, 70, 80, 100]; + const reorderLevels = [0, 5, 10, 15, 20, 25, 30]; + const quantityPerUnit = [ + '100 - 100 g pieces', + '100 - 250 g bags', + '10 - 200 g glasses', + '10 - 4 oz boxes', + '10 - 500 g pkgs.', + '10 - 500 g pkgs.', + '10 boxes x 12 pieces', + '10 boxes x 20 bags', + '10 boxes x 8 pieces', + '10 kg pkg.', + '10 pkgs.', + '12 - 100 g bars', + '12 - 100 g pkgs', + '12 - 12 oz cans', + '12 - 1 lb pkgs.', + '12 - 200 ml jars', + '12 - 250 g pkgs.', + '12 - 355 ml cans', + '12 - 500 g pkgs.', + '750 cc per bottle', + '5 kg pkg.', + '50 bags x 30 sausgs.', + '500 ml', + '500 g', + '48 pieces', + '48 - 6 oz jars', + '4 - 450 g glasses', + '36 boxes', + '32 - 8 oz bottles', + '32 - 500 g boxes', + ]; + const discounts = [0.05, 0.15, 0.2, 0.25]; + + // Need to cast SingleStore database to MySQL database type for compatibility with seed function + // This is because SingleStore adapter is compatible with MySQL but has a different type + const castDb = db as unknown as any; + + // Use seed function to generate data + await seed(castDb, schema).refine((funcs) => ({ + customers: { + count: 10000, + columns: { + companyName: funcs.companyName({}), + contactName: funcs.fullName({}), + contactTitle: funcs.jobTitle({}), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + region: funcs.state({}), + country: funcs.country({}), + phone: funcs.phoneNumber({ template: '(###) ###-####' }), + fax: funcs.phoneNumber({ template: '(###) ###-####' }), + }, + }, + employees: { + count: 200, + columns: { + firstName: funcs.firstName({}), + lastName: funcs.lastName({}), + title: funcs.jobTitle({}), + titleOfCourtesy: funcs.valuesFromArray({ values: titlesOfCourtesy }), + birthDate: funcs.date({ minDate: '1990-01-01', maxDate: '2010-12-31' }), + hireDate: funcs.date({ minDate: '2010-12-31', maxDate: '2024-08-26' }), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + country: funcs.country({}), + homePhone: funcs.phoneNumber({ template: '(###) ###-####' }), + extension: funcs.int({ minValue: 428, maxValue: 5467 }), + notes: funcs.loremIpsum({}), + }, + }, + orders: { + count: 50000, + columns: { + shipVia: funcs.int({ minValue: 1, maxValue: 3 }), + freight: funcs.number({ minValue: 0, maxValue: 1000, precision: 100 }), + shipName: funcs.streetAddress({}), + shipCity: funcs.city({}), + shipRegion: funcs.state({}), + shipPostalCode: funcs.postcode({}), + shipCountry: funcs.country({}), + }, + with: { + details: [ + { weight: 0.6, count: [1, 2, 3, 4] }, + { weight: 0.2, count: [5, 6, 7, 8, 9, 10] }, + { weight: 0.15, count: [11, 12, 13, 14, 15, 16, 17] }, + { weight: 0.05, count: [18, 19, 20, 21, 22, 23, 24, 25] }, + ], + }, + }, + suppliers: { + count: 1000, + columns: { + companyName: funcs.companyName({}), + contactName: funcs.fullName({}), + contactTitle: funcs.jobTitle({}), + address: funcs.streetAddress({}), + city: funcs.city({}), + postalCode: funcs.postcode({}), + region: funcs.state({}), + country: funcs.country({}), + phone: funcs.phoneNumber({ template: '(###) ###-####' }), + }, + }, + products: { + count: 5000, + columns: { + name: funcs.companyName({}), + quantityPerUnit: funcs.valuesFromArray({ values: quantityPerUnit }), + unitPrice: funcs.weightedRandom( + [ + { + weight: 0.5, + value: funcs.int({ minValue: 3, maxValue: 300 }), + }, + { + weight: 0.5, + value: funcs.number({ minValue: 3, maxValue: 300, precision: 100 }), + }, + ], + ), + unitsInStock: funcs.int({ minValue: 0, maxValue: 125 }), + unitsOnOrder: funcs.valuesFromArray({ values: unitsOnOrders }), + reorderLevel: funcs.valuesFromArray({ values: reorderLevels }), + discontinued: funcs.int({ minValue: 0, maxValue: 1 }), + }, + }, + details: { + columns: { + unitPrice: funcs.number({ minValue: 10, maxValue: 130 }), + quantity: funcs.int({ minValue: 1, maxValue: 130 }), + discount: funcs.weightedRandom( + [ + { weight: 0.5, value: funcs.valuesFromArray({ values: discounts }) }, + { weight: 0.5, value: funcs.default({ defaultValue: 0 }) }, + ], + ), + }, + }, + // SingleStore-specific table with vector embeddings + documentEmbeddings: { + count: 500, + columns: { + title: funcs.companyName({}), + content: funcs.loremIpsum({ sentencesCount: 10 }), + // Use default with array of random numbers for embedding + embedding: funcs.default({ + defaultValue: Array.from({ length: 1536 }, () => Math.random() - 0.5), + }), + }, + }, + })); + + console.log('Seed completed successfully.'); + await client.end(); + } catch (error) { + console.error('An error occurred:', error); + console.log('\nNOTE: This script requires a running SingleStore instance.'); + console.log('You can set up SingleStore using the following approaches:'); + console.log('1. SingleStore free tier cloud database at: https://www.singlestore.com/cloud-trial/'); + console.log('2. Local Docker container: docker run -p 3306:3306 --name singlestore -e LICENSE_KEY=your_license_key -e ROOT_PASSWORD=singlestore singlestore/cluster'); + console.log('\nThen set your environment variables:'); + console.log('export SINGLESTORE_HOST=your_host'); + console.log('export SINGLESTORE_PORT=your_port'); + console.log('export SINGLESTORE_USER=your_username'); + console.log('export SINGLESTORE_PASSWORD=your_password'); + process.exit(1); + } +})(); + +// Function to create the necessary tables +async function createSingleStoreSchema(db: any): Promise { + // Drop existing tables if they exist + try { + await db.execute(`DROP TABLE IF EXISTS order_detail;`); + await db.execute(`DROP TABLE IF EXISTS product;`); + await db.execute(`DROP TABLE IF EXISTS order;`); + await db.execute(`DROP TABLE IF EXISTS employee;`); + await db.execute(`DROP TABLE IF EXISTS customer;`); + await db.execute(`DROP TABLE IF EXISTS supplier;`); + await db.execute(`DROP TABLE IF EXISTS document_embedding;`); + } catch (e) { + console.error('Error dropping tables:', e); + } + + // Create tables + await db.execute(` + CREATE TABLE customer ( + id VARCHAR(256) NOT NULL, + company_name TEXT NOT NULL, + contact_name TEXT NOT NULL, + contact_title TEXT NOT NULL, + address TEXT NOT NULL, + city TEXT NOT NULL, + postal_code TEXT, + region TEXT, + country TEXT NOT NULL, + phone TEXT NOT NULL, + fax TEXT, + PRIMARY KEY(id) + ); + `); + + await db.execute(` + CREATE TABLE employee ( + id INT NOT NULL AUTO_INCREMENT, + last_name TEXT NOT NULL, + first_name TEXT, + title TEXT NOT NULL, + title_of_courtesy TEXT NOT NULL, + birth_date TIMESTAMP NOT NULL, + hire_date TIMESTAMP NOT NULL, + address TEXT NOT NULL, + city TEXT NOT NULL, + postal_code TEXT NOT NULL, + country TEXT NOT NULL, + home_phone TEXT NOT NULL, + extension INT NOT NULL, + notes TEXT NOT NULL, + reports_to INT, + photo_path TEXT, + PRIMARY KEY(id) + ); + `); + + await db.execute(` + CREATE TABLE \`order\` ( + id INT NOT NULL AUTO_INCREMENT, + order_date TIMESTAMP NOT NULL, + required_date TIMESTAMP NOT NULL, + shipped_date TIMESTAMP, + ship_via INT NOT NULL, + freight FLOAT NOT NULL, + ship_name TEXT NOT NULL, + ship_city TEXT NOT NULL, + ship_region TEXT, + ship_postal_code TEXT, + ship_country TEXT NOT NULL, + customer_id VARCHAR(256) NOT NULL, + employee_id INT NOT NULL, + PRIMARY KEY(id) + ); + `); + + await db.execute(` + CREATE TABLE supplier ( + id INT NOT NULL AUTO_INCREMENT, + company_name TEXT NOT NULL, + contact_name TEXT NOT NULL, + contact_title TEXT NOT NULL, + address TEXT NOT NULL, + city TEXT NOT NULL, + region TEXT, + postal_code TEXT NOT NULL, + country TEXT NOT NULL, + phone TEXT NOT NULL, + PRIMARY KEY(id) + ); + `); + + await db.execute(` + CREATE TABLE product ( + id INT NOT NULL AUTO_INCREMENT, + name TEXT NOT NULL, + quantity_per_unit TEXT NOT NULL, + unit_price FLOAT NOT NULL, + units_in_stock INT NOT NULL, + units_on_order INT NOT NULL, + reorder_level INT NOT NULL, + discontinued INT NOT NULL, + supplier_id INT NOT NULL, + PRIMARY KEY(id) + ); + `); + + await db.execute(` + CREATE TABLE order_detail ( + unit_price FLOAT NOT NULL, + quantity INT NOT NULL, + discount FLOAT NOT NULL, + order_id INT NOT NULL, + product_id INT NOT NULL + ); + `); + + // SingleStore-specific table with vector support + await db.execute(` + CREATE TABLE document_embedding ( + id INT NOT NULL AUTO_INCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + embedding VECTOR(1536), + PRIMARY KEY(id) + ); + `); +} diff --git a/drizzle-seed/tests/singlestore/allDataTypesTest/singlestoreSchema.ts b/drizzle-seed/tests/singlestore/allDataTypesTest/singlestoreSchema.ts new file mode 100644 index 0000000000..15b8a3af64 --- /dev/null +++ b/drizzle-seed/tests/singlestore/allDataTypesTest/singlestoreSchema.ts @@ -0,0 +1,63 @@ +import { + singlestoreTable, + bigint, + int, + tinyint, + smallint, + mediumint, + real, + decimal, + double, + float, + serial, + binary, + varbinary, + char, + varchar, + text, + boolean, + date, + datetime, + time, + year, + timestamp, + json, + singlestoreEnum, + vector +} from 'drizzle-orm/singlestore-core'; + +// SingleStore enum example +export const popularityEnum = singlestoreEnum('popularity', ['unknown', 'known', 'popular']); + +export const allDataTypes = singlestoreTable('all_data_types', { + integer: int('integer'), + tinyint: tinyint('tinyint'), + smallint: smallint('smallint'), + mediumint: mediumint('mediumint'), + bigint: bigint('bigint', { mode: 'bigint' }), + bigintNumber: bigint('bigint_number', { mode: 'number' }), + real: real('real'), + decimal: decimal('decimal'), + double: double('double'), + float: float('float'), + serial: serial('serial').autoincrement(), + binary: binary('binary', { length: 255 }), + varbinary: varbinary('varbinary', { length: 256 }), + char: char('char', { length: 255 }), + varchar: varchar('varchar', { length: 256 }), + text: text('text'), + boolean: boolean('boolean'), + dateString: date('date_string', { mode: 'string' }), + date: date('date', { mode: 'date' }), + datetime: datetime('datetime', { mode: 'date' }), + datetimeString: datetime('datetimeString', { mode: 'string' }), + time: time('time'), + year: year('year'), + timestampDate: timestamp('timestamp_date', { mode: 'date' }), + timestampString: timestamp('timestamp_string', { mode: 'string' }), + json: json('json'), + popularity: popularityEnum.default('unknown'), + // SingleStore-specific: Vector columns for AI/ML + embedding: vector('embedding', { dimensions: 1536 }), + smallEmbedding: vector('small_embedding', { dimensions: 512 }), +}); diff --git a/drizzle-seed/tests/singlestore/allDataTypesTest/singlestore_all_data_types.test.ts b/drizzle-seed/tests/singlestore/allDataTypesTest/singlestore_all_data_types.test.ts new file mode 100644 index 0000000000..c81a0eb218 --- /dev/null +++ b/drizzle-seed/tests/singlestore/allDataTypesTest/singlestore_all_data_types.test.ts @@ -0,0 +1,246 @@ +// SingleStore All Data Types Test +// This test validates all SingleStore data types are properly handled by Drizzle Seed + +import { describe, test, expect, beforeAll, afterAll, vi } from 'vitest'; +import { SeedService } from '../../../src/services/SeedService.ts'; + +// Mock SingleStore database +const mockDb = { + select: vi.fn().mockReturnThis(), + from: vi.fn().mockReturnValue(Promise.resolve([])), + execute: vi.fn().mockReturnValue(Promise.resolve()), +}; + +let seedService: SeedService; + +beforeAll(async () => { + // Setup for SingleStore all data types testing + seedService = new SeedService(); + vi.clearAllMocks(); +}); + +afterAll(async () => { + // Cleanup +}); + +describe('SingleStore All Data Types Tests', () => { + test('basic seed test for all data types', async () => { + // Mock successful database operations + mockDb.select.mockReturnThis(); + mockDb.from.mockReturnValue(Promise.resolve(Array.from({ length: 1000 }, () => ({})))); + + // Test that all SingleStore data types are properly handled + const mockTables = [{ + name: 'all_data_types', + columns: [ + { name: 'integer', columnType: 'int', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'tinyint', columnType: 'tinyint', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'smallint', columnType: 'smallint', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'mediumint', columnType: 'mediumint', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'bigint', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'bigint_number', columnType: 'bigint', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'real', columnType: 'real', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'decimal', columnType: 'decimal', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'double', columnType: 'double', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'float', columnType: 'float', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'serial', columnType: 'serial', dataType: 'number', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'binary', columnType: 'binary', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'varbinary', columnType: 'varbinary', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'char', columnType: 'char', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'varchar', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'text', columnType: 'text', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'boolean', columnType: 'boolean', dataType: 'boolean', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'date_string', columnType: 'date', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'date', columnType: 'date', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'datetime', columnType: 'datetime', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'datetimeString', columnType: 'datetime', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'time', columnType: 'time', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'year', columnType: 'year', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'timestamp_date', columnType: 'timestamp', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'timestamp_string', columnType: 'timestamp', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'json', columnType: 'json', dataType: 'object', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'popularity', columnType: 'enum', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { values: ['unknown', 'known', 'popular'] } }, + // SingleStore-specific vector columns + { name: 'embedding', columnType: 'vector', dataType: 'array', primary: false, isUnique: false, notNull: false, typeParams: { dimensions: 1536 } }, + { name: 'small_embedding', columnType: 'vector', dataType: 'array', primary: false, isUnique: false, notNull: false, typeParams: { dimensions: 512 } }, + ], + primaryKeys: ['serial'] + }]; + + const mockRelations: any[] = []; + + // Test generating possible generators for all SingleStore data types + expect(() => { + const generators = seedService.generatePossibleGenerators( + 'singlestore', + mockTables as any, + mockRelations, + undefined, + { count: 1000, seed: 123 } + ); + + // Verify generators were created for all column types + expect(generators).toBeDefined(); + expect(Array.isArray(generators)).toBe(true); + }).not.toThrow(); + }); + + test('SingleStore vector column generators', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['id'] }; + + // Test vector with 1536 dimensions (OpenAI embeddings) + const embeddingColumn = { + name: 'embedding', + columnType: 'vector', + dataType: 'array', + primary: false, + isUnique: false, + notNull: false, + typeParams: { dimensions: 1536 } + }; + + const generator1 = seedService.selectGeneratorForSingleStoreColumn(table as any, embeddingColumn as any); + expect(generator1).toBeDefined(); + expect(generator1!.constructor.name).toBe('GenerateArray'); + expect((generator1! as any).params?.size).toBe(1536); + + // Test vector with 512 dimensions + const smallEmbeddingColumn = { + name: 'small_embedding', + columnType: 'vector', + dataType: 'array', + primary: false, + isUnique: false, + notNull: false, + typeParams: { dimensions: 512 } + }; + + const generator2 = seedService.selectGeneratorForSingleStoreColumn(table as any, smallEmbeddingColumn as any); + expect(generator2).toBeDefined(); + expect(generator2!.constructor.name).toBe('GenerateArray'); + expect((generator2! as any).params?.size).toBe(512); + }); + + test('SingleStore enum column generator', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['id'] }; + + const enumColumn = { + name: 'popularity', + columnType: 'enum', + dataType: 'string', + primary: false, + isUnique: false, + notNull: false, + typeParams: { values: ['unknown', 'known', 'popular'] } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, enumColumn as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateValuesFromArray'); + expect((generator! as any).params?.values).toEqual(['unknown', 'known', 'popular']); + }); + + test('SingleStore JSON column generator', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['id'] }; + + const jsonColumn = { + name: 'metadata', + columnType: 'json', + dataType: 'object', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, jsonColumn as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateJson'); + }); + + test('SingleStore serial primary key generator', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['serial'] }; + + const serialColumn = { + name: 'serial', + columnType: 'serial', + dataType: 'number', + primary: true, + isUnique: false, + notNull: true, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, serialColumn as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateIntPrimaryKey'); + }); + + test('SingleStore timestamp columns with different modes', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['id'] }; + + // Test timestamp with date mode + const timestampDateColumn = { + name: 'timestamp_date', + columnType: 'timestamp', + dataType: 'date', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator1 = seedService.selectGeneratorForSingleStoreColumn(table as any, timestampDateColumn as any); + expect(generator1).toBeDefined(); + expect(generator1!.constructor.name).toBe('GenerateTimestamp'); + + // Test timestamp with string mode + const timestampStringColumn = { + name: 'timestamp_string', + columnType: 'timestamp', + dataType: 'string', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator2 = seedService.selectGeneratorForSingleStoreColumn(table as any, timestampStringColumn as any); + expect(generator2).toBeDefined(); + expect(generator2!.constructor.name).toBe('GenerateTimestamp'); + }); + + test('SingleStore numeric types with proper ranges', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['id'] }; + + // Test tinyint + const tinyintColumn = { + name: 'tinyint_col', + columnType: 'tinyint', + dataType: 'number', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const tinyintGenerator = seedService.selectGeneratorForSingleStoreColumn(table as any, tinyintColumn as any); + expect(tinyintGenerator).toBeDefined(); + expect(tinyintGenerator!.constructor.name).toBe('GenerateInt'); + + // Test bigint + const bigintColumn = { + name: 'bigint_col', + columnType: 'bigint', + dataType: 'number', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const bigintGenerator = seedService.selectGeneratorForSingleStoreColumn(table as any, bigintColumn as any); + expect(bigintGenerator).toBeDefined(); + expect(bigintGenerator!.constructor.name).toBe('GenerateInt'); + }); +}); diff --git a/drizzle-seed/tests/singlestore/cyclicTables/singlestore_cyclic_tables.test.ts b/drizzle-seed/tests/singlestore/cyclicTables/singlestore_cyclic_tables.test.ts new file mode 100644 index 0000000000..109762c193 --- /dev/null +++ b/drizzle-seed/tests/singlestore/cyclicTables/singlestore_cyclic_tables.test.ts @@ -0,0 +1,213 @@ +// SingleStore Cyclic Tables Test +// Tests scenarios with circular references adapted for SingleStore (no foreign keys) + +import { describe, test, expect, beforeAll, afterAll, vi } from 'vitest'; +import { SeedService } from '../../../src/services/SeedService.ts'; + +// Mock SingleStore database +const mockDb = { + select: vi.fn().mockReturnThis(), + from: vi.fn().mockReturnValue(Promise.resolve([])), + execute: vi.fn().mockReturnValue(Promise.resolve()), +}; + +let seedService: SeedService; + +beforeAll(async () => { + // Setup for SingleStore cyclic tables testing + seedService = new SeedService(); + vi.clearAllMocks(); +}); + +afterAll(async () => { + // Cleanup +}); + +describe('SingleStore Cyclic Tables Tests', () => { + test('handles tables with potential circular references (soft relations)', async () => { + // Mock successful database operations + mockDb.select.mockReturnThis(); + mockDb.from.mockReturnValue(Promise.resolve(Array.from({ length: 100 }, () => ({})))); + + // Since SingleStore doesn't support foreign keys, we simulate + // soft relations through column references + const mockTables = [ + { + name: 'department', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'manager_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to employee + ], + primaryKeys: ['id'] + }, + { + name: 'employee', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'department_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to department + { name: 'supervisor_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Self-reference + ], + primaryKeys: ['id'] + }, + { + name: 'project', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'lead_employee_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to employee + ], + primaryKeys: ['id'] + }, + { + name: 'employee_project', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'employee_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to employee + { name: 'project_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to project + { name: 'role', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 100 } }, + ], + primaryKeys: ['id'] + } + ]; + + // No actual foreign key relations since SingleStore doesn't support them + const mockRelations: any[] = []; + + // Test that the generator can handle potentially cyclic table structures + expect(() => { + const generators = seedService.generatePossibleGenerators( + 'singlestore', + mockTables as any, + mockRelations, + undefined, + { count: 100, seed: 123 } + ); + + // Verify generators were created + expect(generators).toBeDefined(); + expect(Array.isArray(generators)).toBe(true); + expect(generators.length).toBe(4); // One for each table + }).not.toThrow(); + }); + + test('handles self-referencing table structure', () => { + const table = { + name: 'employee', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'supervisor_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + }; + + // Test that self-referencing columns get appropriate generators + const supervisorColumn = table.columns[2]; + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, supervisorColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateInt'); + }); + + test('handles many-to-many relationship table (junction table)', () => { + const junctionTable = { + name: 'employee_project', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'employee_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'project_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'start_date', columnType: 'date', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'end_date', columnType: 'date', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + }; + + // Test that junction table columns get appropriate generators + const employeeIdColumn = junctionTable.columns[1]; + const projectIdColumn = junctionTable.columns[2]; + + const employeeIdGenerator = seedService.selectGeneratorForSingleStoreColumn(junctionTable as any, employeeIdColumn as any); + const projectIdGenerator = seedService.selectGeneratorForSingleStoreColumn(junctionTable as any, projectIdColumn as any); + + expect(employeeIdGenerator).toBeDefined(); + expect(employeeIdGenerator!.constructor.name).toBe('GenerateInt'); + + expect(projectIdGenerator).toBeDefined(); + expect(projectIdGenerator!.constructor.name).toBe('GenerateInt'); + }); + + test('handles complex organizational structure', () => { + // Test a complex organizational structure with multiple potential cycles + const complexTables = [ + { + name: 'company', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'ceo_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + }, + { + name: 'department', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'company_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'manager_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'parent_department_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + }, + { + name: 'employee', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'department_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'manager_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + } + ]; + + const mockRelations: any[] = []; + + expect(() => { + const generators = seedService.generatePossibleGenerators( + 'singlestore', + complexTables as any, + mockRelations, + undefined, + { count: 50, seed: 456 } + ); + + expect(generators).toBeDefined(); + expect(Array.isArray(generators)).toBe(true); + expect(generators.length).toBe(3); + }).not.toThrow(); + }); + + test('handles tree-like hierarchy (parent-child relationships)', () => { + const hierarchyTable = { + name: 'category', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 256 } }, + { name: 'parent_category_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'level', columnType: 'int', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'path', columnType: 'text', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + }; + + // Test parent reference column + const parentColumn = hierarchyTable.columns[2]; + const generator = seedService.selectGeneratorForSingleStoreColumn(hierarchyTable as any, parentColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateInt'); + }); +}); diff --git a/drizzle-seed/tests/singlestore/generatorsTest/singlestore_generators.test.ts b/drizzle-seed/tests/singlestore/generatorsTest/singlestore_generators.test.ts new file mode 100644 index 0000000000..a10354e128 --- /dev/null +++ b/drizzle-seed/tests/singlestore/generatorsTest/singlestore_generators.test.ts @@ -0,0 +1,361 @@ +// SingleStore Generators Test +// Tests specific generator functionality for SingleStore columns + +import { describe, test, expect, beforeAll, afterAll, vi } from 'vitest'; +import { SeedService } from '../../../src/services/SeedService.ts'; + +// Mock SingleStore database +const _mockDb = { + select: vi.fn().mockReturnThis(), + from: vi.fn().mockReturnValue(Promise.resolve([])), + execute: vi.fn().mockReturnValue(Promise.resolve()), +}; + +let seedService: SeedService; + +beforeAll(async () => { + // Setup for SingleStore generators testing + seedService = new SeedService(); + vi.clearAllMocks(); +}); + +afterAll(async () => { + // Cleanup +}); + +describe('SingleStore Generators Tests', () => { + test('vector generator produces valid embeddings', () => { + const table = { name: 'embeddings_table', columns: [], primaryKeys: ['id'] }; + + // Test vector column with 1536 dimensions (standard OpenAI embedding size) + const vectorColumn = { + name: 'embedding', + columnType: 'vector', + dataType: 'array', + primary: false, + isUnique: false, + notNull: false, + typeParams: { dimensions: 1536 } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, vectorColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateArray'); + + // Check that the generator has the correct size parameter + const params = (generator! as any).params; + expect(params).toBeDefined(); + expect(params.size).toBe(1536); + }); + + test('vector generator with custom dimensions', () => { + const table = { name: 'embeddings_table', columns: [], primaryKeys: ['id'] }; + + // Test vector column with custom dimensions + const customVectorColumn = { + name: 'custom_embedding', + columnType: 'vector', + dataType: 'array', + primary: false, + isUnique: false, + notNull: false, + typeParams: { dimensions: 768 } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, customVectorColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateArray'); + expect((generator! as any).params?.size).toBe(768); + }); + + test('enum generator with SingleStore enum values', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['id'] }; + + const enumColumn = { + name: 'status', + columnType: 'enum', + dataType: 'string', + primary: false, + isUnique: false, + notNull: false, + typeParams: { values: ['active', 'inactive', 'pending', 'suspended'] } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, enumColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateValuesFromArray'); + expect((generator! as any).params?.values).toEqual(['active', 'inactive', 'pending', 'suspended']); + }); + + test('JSON generator for complex data structures', () => { + const table = { name: 'documents_table', columns: [], primaryKeys: ['id'] }; + + const jsonColumn = { + name: 'metadata', + columnType: 'json', + dataType: 'object', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, jsonColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateJson'); + }); + + test('bigint generators for large integer values', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['id'] }; + + // Test bigint with number data type + const bigintNumberColumn = { + name: 'large_number', + columnType: 'bigint', + dataType: 'number', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator1 = seedService.selectGeneratorForSingleStoreColumn(table as any, bigintNumberColumn as any); + expect(generator1).toBeDefined(); + expect(generator1!.constructor.name).toBe('GenerateInt'); + + // Test bigint with bigint data type + const bigintColumn = { + name: 'id_reference', + columnType: 'bigint', + dataType: 'bigint', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator2 = seedService.selectGeneratorForSingleStoreColumn(table as any, bigintColumn as any); + expect(generator2).toBeDefined(); + expect(generator2!.constructor.name).toBe('GenerateInt'); + }); + + test('serial primary key generator for auto-increment', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['id'] }; + + const serialColumn = { + name: 'id', + columnType: 'serial', + dataType: 'number', + primary: true, + isUnique: false, + notNull: true, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, serialColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateIntPrimaryKey'); + }); + + test('timestamp generators with different modes', () => { + const table = { name: 'events_table', columns: [], primaryKeys: ['id'] }; + + // Test timestamp with date mode + const timestampDateColumn = { + name: 'created_at', + columnType: 'timestamp', + dataType: 'date', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator1 = seedService.selectGeneratorForSingleStoreColumn(table as any, timestampDateColumn as any); + expect(generator1).toBeDefined(); + expect(generator1!.constructor.name).toBe('GenerateTimestamp'); + + // Test timestamp with string mode + const timestampStringColumn = { + name: 'updated_at', + columnType: 'timestamp', + dataType: 'string', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator2 = seedService.selectGeneratorForSingleStoreColumn(table as any, timestampStringColumn as any); + expect(generator2).toBeDefined(); + expect(generator2!.constructor.name).toBe('GenerateTimestamp'); + }); + + test('varchar generators with length constraints', () => { + const table = { name: 'test_table', columns: [], primaryKeys: ['id'] }; + + const varcharColumn = { + name: 'description', + columnType: 'varchar', + dataType: 'string', + primary: false, + isUnique: false, + notNull: false, + typeParams: { length: 500 } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, varcharColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateString'); + }); + + test('text generators for large text content', () => { + const table = { name: 'articles_table', columns: [], primaryKeys: ['id'] }; + + const textColumn = { + name: 'content', + columnType: 'text', + dataType: 'string', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, textColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateText'); + }); + + test('boolean generators for true/false values', () => { + const table = { name: 'settings_table', columns: [], primaryKeys: ['id'] }; + + const booleanColumn = { + name: 'is_enabled', + columnType: 'boolean', + dataType: 'boolean', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, booleanColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateBoolean'); + }); + + test('date generators with different modes', () => { + const table = { name: 'events_table', columns: [], primaryKeys: ['id'] }; + + // Test date with date mode + const dateColumn = { + name: 'event_date', + columnType: 'date', + dataType: 'date', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator1 = seedService.selectGeneratorForSingleStoreColumn(table as any, dateColumn as any); + expect(generator1).toBeDefined(); + expect(generator1!.constructor.name).toBe('GenerateDate'); + + // Test date with string mode + const dateStringColumn = { + name: 'event_date_str', + columnType: 'date', + dataType: 'string', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator2 = seedService.selectGeneratorForSingleStoreColumn(table as any, dateStringColumn as any); + expect(generator2).toBeDefined(); + expect(generator2!.constructor.name).toBe('GenerateDate'); + }); + + test('numeric type generators with proper ranges', () => { + const table = { name: 'measurements_table', columns: [], primaryKeys: ['id'] }; + + // Test different numeric types + const numericTypes = [ + { name: 'int_value', columnType: 'int', dataType: 'number' }, + { name: 'tinyint_value', columnType: 'tinyint', dataType: 'number' }, + { name: 'smallint_value', columnType: 'smallint', dataType: 'number' }, + { name: 'mediumint_value', columnType: 'mediumint', dataType: 'number' }, + { name: 'float_value', columnType: 'float', dataType: 'number' }, + { name: 'double_value', columnType: 'double', dataType: 'number' }, + { name: 'decimal_value', columnType: 'decimal', dataType: 'number' }, + { name: 'real_value', columnType: 'real', dataType: 'number' }, + ]; + + for (const type of numericTypes) { + const column = { + name: type.name, + columnType: type.columnType, + dataType: type.dataType, + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, column as any); + expect(generator).toBeDefined(); + + if (type.columnType.includes('float') || type.columnType.includes('double') || + type.columnType.includes('decimal') || type.columnType.includes('real')) { + expect(generator!.constructor.name).toBe('GenerateNumber'); + } else { + expect(generator!.constructor.name).toBe('GenerateInt'); + } + } + }); + + test('binary data generators', () => { + const table = { name: 'files_table', columns: [], primaryKeys: ['id'] }; + + // Test binary column + const binaryColumn = { + name: 'file_data', + columnType: 'binary', + dataType: 'string', + primary: false, + isUnique: false, + notNull: false, + typeParams: { length: 255 } + }; + + const generator1 = seedService.selectGeneratorForSingleStoreColumn(table as any, binaryColumn as any); + expect(generator1).toBeDefined(); + expect(generator1!.constructor.name).toBe('GenerateString'); + + // Test varbinary column + const varbinaryColumn = { + name: 'variable_data', + columnType: 'varbinary', + dataType: 'string', + primary: false, + isUnique: false, + notNull: false, + typeParams: { length: 512 } + }; + + const generator2 = seedService.selectGeneratorForSingleStoreColumn(table as any, varbinaryColumn as any); + expect(generator2).toBeDefined(); + expect(generator2!.constructor.name).toBe('GenerateString'); + }); +}); diff --git a/drizzle-seed/tests/singlestore/singlestore.test.ts b/drizzle-seed/tests/singlestore/singlestore.test.ts new file mode 100644 index 0000000000..d5585995bf --- /dev/null +++ b/drizzle-seed/tests/singlestore/singlestore.test.ts @@ -0,0 +1,323 @@ +// SingleStore Integration Test +// This test demonstrates comprehensive SingleStore support in Drizzle Seed + +import { describe, test, expect, beforeAll, afterAll, afterEach, vi } from 'vitest'; +import { relations } from 'drizzle-orm'; +import { SeedService } from '../../src/services/SeedService.ts'; +import * as schema from './singlestoreSchema.ts'; + +// Mock SingleStore database for comprehensive testing +const mockDb = { + select: vi.fn().mockReturnThis(), + from: vi.fn().mockReturnValue(Promise.resolve([])), + insert: vi.fn().mockReturnThis(), + values: vi.fn().mockReturnValue(Promise.resolve({ affectedRows: 1 })), + $returningId: vi.fn().mockReturnValue(Promise.resolve([{ id: 1 }])), + execute: vi.fn().mockReturnValue(Promise.resolve()), +}; + +describe('SingleStore Seed Integration Tests', () => { + const seedService = new SeedService(); + + beforeAll(async () => { + // Setup mock tables for testing + vi.clearAllMocks(); + }); + + afterAll(async () => { + // Cleanup + }); + + afterEach(async () => { + // Reset mocks after each test + vi.clearAllMocks(); + }); + + // Mock table for testing + const mockTable = { + name: 'test_table', + columns: [], + primaryKeys: ['id'] + }; + + test('should handle SingleStore bigint primary key', () => { + const column = { + name: 'id', + columnType: 'bigint', + dataType: 'number', + primary: true, + isUnique: false, + notNull: true, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, column as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateIntPrimaryKey'); + }); + + test('should handle SingleStore vector columns', () => { + const column = { + name: 'embedding', + columnType: 'vector', + dataType: 'array', + primary: false, + isUnique: false, + notNull: false, + typeParams: { dimensions: 1536 } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, column as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateArray'); + // Check if it has the right size for vector dimensions + expect((generator! as any).params?.size).toBe(1536); + }); + + test('should handle SingleStore varchar columns', () => { + const column = { + name: 'name', + columnType: 'varchar', + dataType: 'string', + primary: false, + isUnique: false, + notNull: true, + typeParams: { length: 255 } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, column as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateFirstName'); + }); + + test('should handle SingleStore email columns', () => { + const column = { + name: 'email', + columnType: 'varchar', + dataType: 'string', + primary: false, + isUnique: false, + notNull: true, + typeParams: { length: 255 } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, column as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateEmail'); + }); + + test('should handle SingleStore json columns', () => { + const column = { + name: 'metadata', + columnType: 'json', + dataType: 'object', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, column as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateJson'); + }); + + test('should handle SingleStore boolean columns', () => { + const column = { + name: 'isActive', + columnType: 'boolean', + dataType: 'boolean', + primary: false, + isUnique: false, + notNull: true, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, column as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateBoolean'); + }); + + test('should handle SingleStore timestamp columns', () => { + const column = { + name: 'createdAt', + columnType: 'timestamp', + dataType: 'string', + primary: false, + isUnique: false, + notNull: true, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, column as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateTimestamp'); + }); + + test('should handle SingleStore int columns with proper ranges', () => { + const column = { + name: 'count', + columnType: 'int', + dataType: 'number', + primary: false, + isUnique: false, + notNull: true, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, column as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateInt'); + // Check if it has proper int range + expect((generator! as any).params?.minValue).toBe(-2147483648); + expect((generator! as any).params?.maxValue).toBe(2147483647); + }); + + test('should handle SingleStore decimal columns with precision', () => { + const column = { + name: 'price', + columnType: 'decimal', + dataType: 'number', + primary: false, + isUnique: false, + notNull: true, + typeParams: { precision: 10, scale: 2 } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, column as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateNumber'); + // Check precision handling - the precision is calculated as 10^scale = 10^2 = 100 + expect((generator! as any).params?.precision).toBe(100); // Math.pow(10, scale) + }); + + test('should support singlestore connection type in generatePossibleGenerators', () => { + // Test that 'singlestore' is accepted as a connection type + const mockTables = [mockTable]; + const mockRelations: any[] = []; + + expect(() => { + seedService.generatePossibleGenerators( + 'singlestore', + mockTables as any, + mockRelations, + undefined, + { count: 10, seed: 123 } + ); + }).not.toThrow(); + }); + + // Integration tests with mock database + test('basic seed test with mock database', async () => { + // Mock the database calls to return appropriate data + mockDb.select.mockReturnThis(); + mockDb.from.mockReturnValue(Promise.resolve(Array.from({ length: 10 }, () => ({})))); + + // Test seeding with schema (would work with real SingleStore database) + expect(() => { + // This tests the generator selection and parameter handling + seedService.generatePossibleGenerators( + 'singlestore', + Object.values(schema).map(table => ({ name: table._.name, columns: [], primaryKeys: ['id'] })) as any, + [], + undefined, + { count: 10, seed: 123 } + ); + }).not.toThrow(); + }); + + test('seed with custom count for SingleStore', async () => { + const customCount = 11; + + expect(() => { + seedService.generatePossibleGenerators( + 'singlestore', + [mockTable] as any, + [], + undefined, + { count: customCount, seed: 123 } + ); + }).not.toThrow(); + }); + + test('SingleStore parameter limit handling', () => { + // Test that SingleStore uses correct parameter limits (100,000 vs MySQL's 65,535) + const mockRelations: any[] = []; + const mockGenerators = seedService.generatePossibleGenerators( + 'singlestore', + [mockTable] as any, + mockRelations, + undefined, + { count: 1000, seed: 123 } + ); + + const result = seedService.generateTablesValues( + mockRelations, + mockGenerators, + undefined, + undefined, + { count: 1000, seed: 123 } + ); + + // Should not throw error for large datasets due to proper parameter limit handling + expect(result).toBeDefined(); + }); + + test('SingleStore vector column generation', () => { + const vectorColumn = { + name: 'embedding', + columnType: 'vector', + dataType: 'array', + primary: false, + isUnique: false, + notNull: true, + typeParams: { dimensions: 512 } + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, vectorColumn as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateArray'); + expect((generator! as any).params?.size).toBe(512); + }); + + test('SingleStore JSON metadata column', () => { + const jsonColumn = { + name: 'metadata', + columnType: 'json', + dataType: 'object', + primary: false, + isUnique: false, + notNull: false, + typeParams: {} + }; + + const generator = seedService.selectGeneratorForSingleStoreColumn(mockTable as any, jsonColumn as any); + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateJson'); + }); + + test('overlapping soft relations without foreign keys', async () => { + // Test soft relations since SingleStore doesn't support foreign keys + const _postsRelation = relations(schema.posts, ({ one }) => ({ + user: one(schema.users, { fields: [schema.posts.userId], references: [schema.users.id] }), + })); + + const consoleMock = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + // This should work without foreign key constraints + expect(() => { + seedService.generatePossibleGenerators( + 'singlestore', + [ + { name: 'users', columns: [], primaryKeys: ['id'] }, + { name: 'posts', columns: [], primaryKeys: ['id'] } + ] as any, + [], + undefined, + { count: 10, seed: 123 } + ); + }).not.toThrow(); + + consoleMock.mockRestore(); + }); +}); diff --git a/drizzle-seed/tests/singlestore/singlestoreSchema.ts b/drizzle-seed/tests/singlestore/singlestoreSchema.ts new file mode 100644 index 0000000000..06492d8f80 --- /dev/null +++ b/drizzle-seed/tests/singlestore/singlestoreSchema.ts @@ -0,0 +1,130 @@ +import { + singlestoreTable, + bigint, + float, + text, + timestamp, + varchar, + int, + json, + vector +} from 'drizzle-orm/singlestore-core'; + +export const customers = singlestoreTable('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = singlestoreTable( + 'employee', + { + id: bigint('id', { mode: 'number' }).primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: int('extension').notNull(), + notes: text('notes').notNull(), + reportsTo: bigint('reports_to', { mode: 'number' }), // No foreign key constraint + photoPath: text('photo_path'), + }, +); + +export const orders = singlestoreTable('order', { + id: bigint('id', { mode: 'number' }).primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: int('ship_via').notNull(), + freight: float('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + customerId: varchar('customer_id', { length: 256 }).notNull(), // No foreign key constraint + + employeeId: bigint('employee_id', { mode: 'number' }).notNull(), // No foreign key constraint +}); + +export const suppliers = singlestoreTable('supplier', { + id: bigint('id', { mode: 'number' }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = singlestoreTable('product', { + id: bigint('id', { mode: 'number' }).primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: float('unit_price').notNull(), + unitsInStock: int('units_in_stock').notNull(), + unitsOnOrder: int('units_on_order').notNull(), + reorderLevel: int('reorder_level').notNull(), + discontinued: int('discontinued').notNull(), + + supplierId: bigint('supplier_id', { mode: 'number' }).notNull(), // No foreign key constraint +}); + +export const details = singlestoreTable('order_detail', { + unitPrice: float('unit_price').notNull(), + quantity: int('quantity').notNull(), + discount: float('discount').notNull(), + + orderId: bigint('order_id', { mode: 'number' }).notNull(), // No foreign key constraint + + productId: bigint('product_id', { mode: 'number' }).notNull(), // No foreign key constraint +}); + +export const users = singlestoreTable( + 'users', + { + id: bigint('id', { mode: 'number' }).primaryKey(), + name: text('name'), + invitedBy: bigint('invitedBy', { mode: 'number' }), // No foreign key constraint + }, +); + +export const posts = singlestoreTable( + 'posts', + { + id: bigint('id', { mode: 'number' }).primaryKey(), + name: text('name'), + content: text('content'), + userId: bigint('userId', { mode: 'number' }), // No foreign key constraint + }, +); + +// SingleStore-specific tables with vector support and JSON +export const aiDocuments = singlestoreTable('ai_documents', { + id: bigint('id', { mode: 'number' }).primaryKey(), + title: varchar('title', { length: 255 }).notNull(), + content: text('content').notNull(), + embedding: vector('embedding', { dimensions: 1536 }), // OpenAI embeddings + metadata: json('metadata'), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); diff --git a/drizzle-seed/tests/singlestore/softRelationsTest/singlestore_soft_relations.test.ts b/drizzle-seed/tests/singlestore/softRelationsTest/singlestore_soft_relations.test.ts new file mode 100644 index 0000000000..0d66e3a460 --- /dev/null +++ b/drizzle-seed/tests/singlestore/softRelationsTest/singlestore_soft_relations.test.ts @@ -0,0 +1,357 @@ +// SingleStore Soft Relations Test +// Tests soft relationship handling since SingleStore doesn't support foreign keys + +import { describe, test, expect, beforeAll, afterAll, vi } from 'vitest'; +import { SeedService } from '../../../src/services/SeedService.ts'; + +// Mock SingleStore database +const _mockDb = { + select: vi.fn().mockReturnThis(), + from: vi.fn().mockReturnValue(Promise.resolve([])), + execute: vi.fn().mockReturnValue(Promise.resolve()), +}; + +let seedService: SeedService; + +beforeAll(async () => { + // Setup for SingleStore soft relations testing + seedService = new SeedService(); + vi.clearAllMocks(); +}); + +afterAll(async () => { + // Cleanup +}); + +describe('SingleStore Soft Relations Tests', () => { + test('handles soft one-to-many relationships', async () => { + // In SingleStore, relationships are "soft" - managed by application logic + // rather than database foreign key constraints + const mockTables = [ + { + name: 'users', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'username', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 100 } }, + { name: 'email', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'created_at', columnType: 'timestamp', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + }, + { + name: 'posts', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'title', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'content', columnType: 'text', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'user_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to users.id + { name: 'published_at', columnType: 'timestamp', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + } + ]; + + // No foreign key relations since SingleStore doesn't support them + const mockRelations: any[] = []; + + expect(() => { + const generators = seedService.generatePossibleGenerators( + 'singlestore', + mockTables as any, + mockRelations, + undefined, + { count: 100, seed: 123 } + ); + + expect(generators).toBeDefined(); + expect(Array.isArray(generators)).toBe(true); + expect(generators.length).toBe(2); + }).not.toThrow(); + }); + + test('handles soft many-to-many relationships through junction tables', () => { + const mockTables = [ + { + name: 'categories', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 100 } }, + { name: 'description', columnType: 'text', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + }, + { + name: 'products', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'price', columnType: 'decimal', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: { precision: 10, scale: 2 } }, + ], + primaryKeys: ['id'] + }, + { + name: 'product_categories', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'product_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to products.id + { name: 'category_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to categories.id + { name: 'created_at', columnType: 'timestamp', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + } + ]; + + const mockRelations: any[] = []; + + expect(() => { + const generators = seedService.generatePossibleGenerators( + 'singlestore', + mockTables as any, + mockRelations, + undefined, + { count: 50, seed: 456 } + ); + + expect(generators).toBeDefined(); + expect(Array.isArray(generators)).toBe(true); + expect(generators.length).toBe(3); + }).not.toThrow(); + }); + + test('handles soft self-referencing relationships', () => { + const table = { + name: 'employees', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'manager_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Self-reference + { name: 'department', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 100 } }, + ], + primaryKeys: ['id'] + }; + + // Test that self-referencing columns get appropriate generators + const managerIdColumn = table.columns[2]; + const generator = seedService.selectGeneratorForSingleStoreColumn(table as any, managerIdColumn as any); + + expect(generator).toBeDefined(); + expect(generator!.constructor.name).toBe('GenerateInt'); + }); + + test('handles polymorphic relationships through type columns', () => { + const mockTables = [ + { + name: 'users', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + ], + primaryKeys: ['id'] + }, + { + name: 'companies', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + ], + primaryKeys: ['id'] + }, + { + name: 'addresses', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'street', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'city', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 100 } }, + { name: 'addressable_type', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 50 } }, // 'user' or 'company' + { name: 'addressable_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // references users.id or companies.id + ], + primaryKeys: ['id'] + } + ]; + + const mockRelations: any[] = []; + + expect(() => { + const generators = seedService.generatePossibleGenerators( + 'singlestore', + mockTables as any, + mockRelations, + undefined, + { count: 75, seed: 789 } + ); + + expect(generators).toBeDefined(); + expect(Array.isArray(generators)).toBe(true); + expect(generators.length).toBe(3); + }).not.toThrow(); + + // Test specific columns for polymorphic relationship + const addressesTable = mockTables[2]!; + const typeColumn = addressesTable.columns[3]; // addressable_type + const idColumn = addressesTable.columns[4]; // addressable_id + + const typeGenerator = seedService.selectGeneratorForSingleStoreColumn(addressesTable as any, typeColumn as any); + const idGenerator = seedService.selectGeneratorForSingleStoreColumn(addressesTable as any, idColumn as any); + + expect(typeGenerator).toBeDefined(); + expect(typeGenerator!.constructor.name).toBe('GenerateString'); + + expect(idGenerator).toBeDefined(); + expect(idGenerator!.constructor.name).toBe('GenerateInt'); + }); + + test('handles soft relationships with JSON metadata', () => { + const mockTables = [ + { + name: 'orders', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'customer_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference + { name: 'total_amount', columnType: 'decimal', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: { precision: 10, scale: 2 } }, + { name: 'metadata', columnType: 'json', dataType: 'object', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'created_at', columnType: 'timestamp', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + }, + { + name: 'order_items', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'order_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to orders.id + { name: 'product_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference + { name: 'quantity', columnType: 'int', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'unit_price', columnType: 'decimal', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: { precision: 10, scale: 2 } }, + { name: 'item_metadata', columnType: 'json', dataType: 'object', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + } + ]; + + const mockRelations: any[] = []; + + expect(() => { + const generators = seedService.generatePossibleGenerators( + 'singlestore', + mockTables as any, + mockRelations, + undefined, + { count: 200, seed: 321 } + ); + + expect(generators).toBeDefined(); + expect(Array.isArray(generators)).toBe(true); + expect(generators.length).toBe(2); + }).not.toThrow(); + + // Test JSON metadata columns specifically + const ordersTable = mockTables[0]!; + const orderMetadataColumn = ordersTable.columns[3]; + + const metadataGenerator = seedService.selectGeneratorForSingleStoreColumn(ordersTable as any, orderMetadataColumn as any); + expect(metadataGenerator).toBeDefined(); + expect(metadataGenerator!.constructor.name).toBe('GenerateJson'); + }); + + test('handles soft relationships with vector embeddings', () => { + const mockTables = [ + { + name: 'documents', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'title', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'content', columnType: 'text', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'author_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference + { name: 'embedding', columnType: 'vector', dataType: 'array', primary: false, isUnique: false, notNull: false, typeParams: { dimensions: 1536 } }, + { name: 'category_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference + ], + primaryKeys: ['id'] + }, + { + name: 'document_similarities', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'document_a_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference + { name: 'document_b_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference + { name: 'similarity_score', columnType: 'float', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'computed_at', columnType: 'timestamp', dataType: 'date', primary: false, isUnique: false, notNull: false, typeParams: {} }, + ], + primaryKeys: ['id'] + } + ]; + + const mockRelations: any[] = []; + + expect(() => { + const generators = seedService.generatePossibleGenerators( + 'singlestore', + mockTables as any, + mockRelations, + undefined, + { count: 150, seed: 654 } + ); + + expect(generators).toBeDefined(); + expect(Array.isArray(generators)).toBe(true); + expect(generators.length).toBe(2); + }).not.toThrow(); + + // Test vector embedding column specifically + const documentsTable = mockTables[0]!; + const embeddingColumn = documentsTable.columns[4]; + + const embeddingGenerator = seedService.selectGeneratorForSingleStoreColumn(documentsTable as any, embeddingColumn as any); + expect(embeddingGenerator).toBeDefined(); + expect(embeddingGenerator!.constructor.name).toBe('GenerateArray'); + expect((embeddingGenerator! as any).params?.size).toBe(1536); + }); + + test('handles soft relationships in hierarchical data structures', () => { + const mockTables = [ + { + name: 'categories', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'parent_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Self-reference (soft) + { name: 'level', columnType: 'int', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: {} }, + { name: 'path', columnType: 'text', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Materialized path + ], + primaryKeys: ['id'] + }, + { + name: 'products', + columns: [ + { name: 'id', columnType: 'bigint', dataType: 'bigint', primary: true, isUnique: false, notNull: true, typeParams: {} }, + { name: 'name', columnType: 'varchar', dataType: 'string', primary: false, isUnique: false, notNull: false, typeParams: { length: 255 } }, + { name: 'category_id', columnType: 'bigint', dataType: 'bigint', primary: false, isUnique: false, notNull: false, typeParams: {} }, // Soft reference to categories.id + { name: 'price', columnType: 'decimal', dataType: 'number', primary: false, isUnique: false, notNull: false, typeParams: { precision: 10, scale: 2 } }, + ], + primaryKeys: ['id'] + } + ]; + + const mockRelations: any[] = []; + + expect(() => { + const generators = seedService.generatePossibleGenerators( + 'singlestore', + mockTables as any, + mockRelations, + undefined, + { count: 100, seed: 987 } + ); + + expect(generators).toBeDefined(); + expect(Array.isArray(generators)).toBe(true); + expect(generators.length).toBe(2); + }).not.toThrow(); + + // Test hierarchical parent reference + const categoriesTable = mockTables[0]!; + const parentColumn = categoriesTable.columns[2]; + + const parentGenerator = seedService.selectGeneratorForSingleStoreColumn(categoriesTable as any, parentColumn as any); + expect(parentGenerator).toBeDefined(); + expect(parentGenerator!.constructor.name).toBe('GenerateInt'); + }); +}); diff --git a/drizzle-seed/type-tests/singlestore.ts b/drizzle-seed/type-tests/singlestore.ts new file mode 100644 index 0000000000..60ffd74f6e --- /dev/null +++ b/drizzle-seed/type-tests/singlestore.ts @@ -0,0 +1,44 @@ + +import { + bigint, + boolean, + json, + singlestoreTable, + text, + timestamp, + varchar, + vector +} from 'drizzle-orm/singlestore-core'; +import { drizzle as singlestoreDrizzle } from 'drizzle-orm/singlestore'; +import { reset, seed } from '../src/index.ts'; + +// Basic users table +const singlestoreUsers = singlestoreTable('users', { + id: bigint('id', { mode: 'number' }).primaryKey().autoincrement(), + name: text('name'), + email: varchar('email', { length: 255 }), + isActive: boolean('is_active').default(true), + createdAt: timestamp('created_at').defaultNow(), + // SingleStore doesn't support references with this syntax + inviteId: bigint('invite_id', { mode: 'number' }), +}); + +// Documents table with vector embedding +const singlestoreDocuments = singlestoreTable('documents', { + id: bigint('id', { mode: 'number' }).primaryKey().autoincrement(), + title: text('title'), + content: text('content'), + embedding: vector('embedding', { dimensions: 1536 }), + userId: bigint('user_id', { mode: 'number' }), + metadata: json('metadata'), +}); + +{ + const db = singlestoreDrizzle(''); + + await seed(db, { users: singlestoreUsers }); + await reset(db, { users: singlestoreUsers }); + + await seed(db, { documents: singlestoreDocuments }); + await reset(db, { documents: singlestoreDocuments }); +} diff --git a/drizzle-seed/vitest.config.ts b/drizzle-seed/vitest.config.ts index 5489010bde..fc923341d2 100644 --- a/drizzle-seed/vitest.config.ts +++ b/drizzle-seed/vitest.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ './tests/pg/**/*.test.ts', './tests/mysql/**/*.test.ts', './tests/sqlite/**/*.test.ts', + './tests/singlestore/**/*.test.ts', ], exclude: [], typecheck: { diff --git a/integration-tests/tests/seeder/singlestore.test.ts b/integration-tests/tests/seeder/singlestore.test.ts new file mode 100644 index 0000000000..6d4779d95e --- /dev/null +++ b/integration-tests/tests/seeder/singlestore.test.ts @@ -0,0 +1,464 @@ +import { sql } from 'drizzle-orm'; +import type { MySqlDatabase } from 'drizzle-orm/mysql-core'; +import type { SingleStoreDatabase } from 'drizzle-orm/singlestore-core'; +import { drizzle } from 'drizzle-orm/singlestore'; +import * as mysql2 from 'mysql2/promise'; +import { reset, seed } from 'drizzle-seed'; +import { afterAll, afterEach, beforeAll, expect, test } from 'vitest'; +import * as schema from './singlestoreSchema.ts'; + +let client: mysql2.Connection; +let db: SingleStoreDatabase; + +// Helper function to cast the SingleStore database to work with seed/reset +// This is needed because drizzle-seed expects MySqlDatabase with a mode property +function asMySQLDatabase>(database: T): MySqlDatabase { + return database as unknown as MySqlDatabase; +} + +// Get connection to SingleStore +async function getClient() { + const connectionString = process.env['SINGLESTORE_CONNECTION_STRING'] ?? + 'singlestore://root:singlestore@localhost:3306/drizzle'; + + console.log('Connecting to SingleStore with connection string:', connectionString); + + try { + const client = await mysql2.createConnection({ + uri: connectionString, + supportBigNumbers: true, + // Set shorter timeout for local dev + connectTimeout: 5000, + }); + + await client.query(`CREATE DATABASE IF NOT EXISTS drizzle;`); + await client.changeUser({ database: 'drizzle' }); + + return { client }; + } catch (error) { + console.error('Error connecting to SingleStore:', error); + throw error; + } +} + +async function createSingleStoreSchema(): Promise { + await createNorthwindTables(); + await createAllDataTypesTable(); + await createAllGeneratorsTables(); +} + +const createNorthwindTables = async () => { + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS customer ( + id VARCHAR(256) NOT NULL, + company_name TEXT NOT NULL, + contact_name TEXT NOT NULL, + contact_title TEXT NOT NULL, + address TEXT NOT NULL, + city TEXT NOT NULL, + postal_code TEXT, + region TEXT, + country TEXT NOT NULL, + phone TEXT NOT NULL, + fax TEXT, + PRIMARY KEY(id) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS order_detail ( + unit_price FLOAT NOT NULL, + quantity INT NOT NULL, + discount FLOAT NOT NULL, + order_id INT NOT NULL, + product_id INT NOT NULL + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS employee ( + id INT NOT NULL, + last_name TEXT NOT NULL, + first_name TEXT, + title TEXT NOT NULL, + title_of_courtesy TEXT NOT NULL, + birth_date TIMESTAMP NOT NULL, + hire_date TIMESTAMP NOT NULL, + address TEXT NOT NULL, + city TEXT NOT NULL, + postal_code TEXT NOT NULL, + country TEXT NOT NULL, + home_phone TEXT NOT NULL, + extension INT NOT NULL, + notes TEXT NOT NULL, + reports_to INT, + photo_path TEXT, + PRIMARY KEY(id) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS \`order\` ( + id INT NOT NULL, + order_date TIMESTAMP NOT NULL, + required_date TIMESTAMP NOT NULL, + shipped_date TIMESTAMP, + ship_via INT NOT NULL, + freight FLOAT NOT NULL, + ship_name TEXT NOT NULL, + ship_city TEXT NOT NULL, + ship_region TEXT, + ship_postal_code TEXT, + ship_country TEXT NOT NULL, + customer_id VARCHAR(256) NOT NULL, + employee_id INT NOT NULL, + PRIMARY KEY(id) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS product ( + id INT NOT NULL, + name TEXT NOT NULL, + quantity_per_unit TEXT NOT NULL, + unit_price FLOAT NOT NULL, + units_in_stock INT NOT NULL, + units_on_order INT NOT NULL, + reorder_level INT NOT NULL, + discontinued INT NOT NULL, + supplier_id INT NOT NULL, + PRIMARY KEY(id) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS supplier ( + id INT NOT NULL, + company_name TEXT NOT NULL, + contact_name TEXT NOT NULL, + contact_title TEXT NOT NULL, + address TEXT NOT NULL, + city TEXT NOT NULL, + region TEXT, + postal_code TEXT NOT NULL, + country TEXT NOT NULL, + phone TEXT NOT NULL, + PRIMARY KEY(id) + ); + `, + ); + + // SingleStore doesn't support foreign key constraints, so we skip those +}; + +const createAllDataTypesTable = async () => { + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS all_data_types ( + integer INT, + tinyint TINYINT, + smallint SMALLINT, + mediumint MEDIUMINT, + bigint BIGINT, + bigint_number BIGINT, + real REAL, + decimal DECIMAL, + double DOUBLE, + float FLOAT, + serial SERIAL AUTO_INCREMENT, + binary BINARY(255), + varbinary VARBINARY(256), + char CHAR(255), + varchar VARCHAR(256), + text TEXT, + boolean BOOLEAN, + date_string DATE, + date DATE, + datetime DATETIME, + datetimeString DATETIME, + time TIME, + timestamp_date TIMESTAMP, + timestamp_string TIMESTAMP, + json JSON, + embedding VECTOR(128) + ); + `, + ); +}; + +const createAllGeneratorsTables = async () => { + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS datetime_table ( + datetime DATETIME + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS vector_table ( + id INT NOT NULL AUTO_INCREMENT, + embedding VECTOR(1536), + PRIMARY KEY(id) + ); + `, + ); + + await db.execute( + sql` + CREATE TABLE IF NOT EXISTS document_table ( + id INT NOT NULL AUTO_INCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + metadata JSON, + embedding VECTOR(768), + PRIMARY KEY(id) + ); + `, + ); +}; + +beforeAll(async () => { + // Get SingleStore client from common utility + const result = await getClient(); + client = result.client; + db = drizzle(client); + + // Create schema for tests + await createSingleStoreSchema(); +}); + +afterAll(async () => { + await client.end().catch(console.error); +}); + +afterEach(async () => { + await reset(asMySQLDatabase(db), schema); +}); + +test('basic seed test for SingleStore', async () => { + await seed(asMySQLDatabase(db), schema); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(10); + expect(details.length).toBe(10); + expect(employees.length).toBe(10); + expect(orders.length).toBe(10); + expect(products.length).toBe(10); + expect(suppliers.length).toBe(10); +}); + +test('seed with options.count:11 test for SingleStore', async () => { + await seed(asMySQLDatabase(db), schema, { count: 11 }); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(11); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) customers count for SingleStore', async () => { + await seed(asMySQLDatabase(db), schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(11); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test('redefine(refine) all tables count for SingleStore', async () => { + await seed(asMySQLDatabase(db), schema, { count: 11 }).refine(() => ({ + customers: { + count: 12, + }, + details: { + count: 13, + }, + employees: { + count: 14, + }, + orders: { + count: 15, + }, + products: { + count: 16, + }, + suppliers: { + count: 17, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(12); + expect(details.length).toBe(13); + expect(employees.length).toBe(14); + expect(orders.length).toBe(15); + expect(products.length).toBe(16); + expect(suppliers.length).toBe(17); +}); + +test("redefine(refine) orders count using 'with' in customers for SingleStore", async () => { + await seed(asMySQLDatabase(db), schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 13, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(11); + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +test("sequential using of 'with' in SingleStore", async () => { + await seed(asMySQLDatabase(db), schema, { count: 11 }).refine(() => ({ + customers: { + count: 4, + with: { + orders: 2, + }, + }, + orders: { + count: 12, + with: { + details: 3, + }, + }, + })); + + const customers = await db.select().from(schema.customers); + const details = await db.select().from(schema.details); + const employees = await db.select().from(schema.employees); + const orders = await db.select().from(schema.orders); + const products = await db.select().from(schema.products); + const suppliers = await db.select().from(schema.suppliers); + + expect(customers.length).toBe(4); + expect(details.length).toBe(24); // 8 orders × 3 details + expect(employees.length).toBe(11); + expect(orders.length).toBe(8); + expect(products.length).toBe(11); + expect(suppliers.length).toBe(11); +}); + +// All data types test ------------------------------- +test('basic seed test for all SingleStore data types', async () => { + await seed(asMySQLDatabase(db), schema, { count: 10 }); + + const allDataTypes = await db.select().from(schema.allDataTypes); + + // every value in each row (except possibly null fields) should not be undefined + const predicate = allDataTypes.every((row: any) => + Object.values(row).some((val: any) => val !== undefined) + ); + + expect(predicate).toBe(true); + expect(allDataTypes.length).toBe(10); +}); + +// SingleStore vector tests ------------------------------- +test('vector column generation in SingleStore', async () => { + await seed(asMySQLDatabase(db), { vectorTable: schema.vectorTable }, { count: 5 }); + + const vectors = await db.select().from(schema.vectorTable); + + expect(vectors.length).toBe(5); + // Check that embeddings were generated with correct dimensions + expect(vectors[0]!.embedding).toBeDefined(); + expect(Array.isArray(vectors[0]!.embedding)).toBe(true); + expect(vectors[0]!.embedding!.length).toBe(1536); +}); + +test('document with embedding in SingleStore', async () => { + await seed(asMySQLDatabase(db), { documentTable: schema.documentTable }, { count: 8 }); + + const documents = await db.select().from(schema.documentTable); + + expect(documents.length).toBe(8); + // Check document fields + expect(documents[0]!.title).toBeDefined(); + expect(documents[0]!.content).toBeDefined(); + // Check embeddings with correct dimensions + expect(documents[0]!.embedding).toBeDefined(); + expect(Array.isArray(documents[0]!.embedding)).toBe(true); + expect(documents[0]!.embedding!.length).toBe(768); +}); + +test('datetime generator test for SingleStore', async () => { + await seed(asMySQLDatabase(db), { datetimeTable: schema.datetimeTable }).refine((funcs) => ({ + datetimeTable: { + count: 20, + columns: { + datetime: funcs.datetime(), + }, + }, + })); + + const data = await db.select().from(schema.datetimeTable); + // every value should be defined + const predicate = data.length === 20 && + data.every((row: any) => row.datetime !== undefined && row.datetime !== null); + + expect(predicate).toBe(true); +}); diff --git a/integration-tests/tests/seeder/singlestoreSchema.ts b/integration-tests/tests/seeder/singlestoreSchema.ts new file mode 100644 index 0000000000..a2693f754c --- /dev/null +++ b/integration-tests/tests/seeder/singlestoreSchema.ts @@ -0,0 +1,169 @@ +// Import singlestore-core modules for schema definition +import { + bigint, + binary, + boolean, + char, + date, + datetime, + decimal, + double, + float, + int, + json, + mediumint, + real, + serial, + smallint, + singlestoreTable, + text, + time, + timestamp, + tinyint, + varbinary, + varchar, + vector, +} from 'drizzle-orm/singlestore-core'; + +export const customers = singlestoreTable('customer', { + id: varchar('id', { length: 256 }).primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code'), + region: text('region'), + country: text('country').notNull(), + phone: text('phone').notNull(), + fax: text('fax'), +}); + +export const employees = singlestoreTable( + 'employee', + { + id: int('id').primaryKey(), + lastName: text('last_name').notNull(), + firstName: text('first_name'), + title: text('title').notNull(), + titleOfCourtesy: text('title_of_courtesy').notNull(), + birthDate: timestamp('birth_date').notNull(), + hireDate: timestamp('hire_date').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + homePhone: text('home_phone').notNull(), + extension: int('extension').notNull(), + notes: text('notes').notNull(), + // SingleStore doesn't support references with circular table references + reportsTo: int('reports_to'), + photoPath: text('photo_path'), + }, +); + +export const orders = singlestoreTable('order', { + id: int('id').primaryKey(), + orderDate: timestamp('order_date').notNull(), + requiredDate: timestamp('required_date').notNull(), + shippedDate: timestamp('shipped_date'), + shipVia: int('ship_via').notNull(), + freight: float('freight').notNull(), + shipName: text('ship_name').notNull(), + shipCity: text('ship_city').notNull(), + shipRegion: text('ship_region'), + shipPostalCode: text('ship_postal_code'), + shipCountry: text('ship_country').notNull(), + + // SingleStore doesn't support foreign key constraints + customerId: varchar('customer_id', { length: 256 }).notNull(), + employeeId: int('employee_id').notNull(), +}); + +export const suppliers = singlestoreTable('supplier', { + id: int('id').primaryKey(), + companyName: text('company_name').notNull(), + contactName: text('contact_name').notNull(), + contactTitle: text('contact_title').notNull(), + address: text('address').notNull(), + city: text('city').notNull(), + region: text('region'), + postalCode: text('postal_code').notNull(), + country: text('country').notNull(), + phone: text('phone').notNull(), +}); + +export const products = singlestoreTable('product', { + id: int('id').primaryKey(), + name: text('name').notNull(), + quantityPerUnit: text('quantity_per_unit').notNull(), + unitPrice: float('unit_price').notNull(), + unitsInStock: int('units_in_stock').notNull(), + unitsOnOrder: int('units_on_order').notNull(), + reorderLevel: int('reorder_level').notNull(), + discontinued: int('discontinued').notNull(), + + // SingleStore doesn't support foreign key constraints + supplierId: int('supplier_id').notNull(), +}); + +export const details = singlestoreTable('order_detail', { + unitPrice: float('unit_price').notNull(), + quantity: int('quantity').notNull(), + discount: float('discount').notNull(), + + // SingleStore doesn't support foreign key constraints + orderId: int('order_id').notNull(), + productId: int('product_id').notNull(), +}); + +// All data types table ------------------------------- +export const allDataTypes = singlestoreTable('all_data_types', { + int: int('integer'), + tinyint: tinyint('tinyint'), + smallint: smallint('smallint'), + mediumint: mediumint('mediumint'), + biginteger: bigint('bigint', { mode: 'bigint' }), + bigintNumber: bigint('bigint_number', { mode: 'number' }), + real: real('real'), + decimal: decimal('decimal'), + double: double('double'), + float: float('float'), + serial: serial('serial'), + binary: binary('binary', { length: 255 }), + varbinary: varbinary('varbinary', { length: 256 }), + char: char('char', { length: 255 }), + varchar: varchar('varchar', { length: 256 }), + text: text('text'), + boolean: boolean('boolean'), + dateString: date('date_string', { mode: 'string' }), + date: date('date', { mode: 'date' }), + datetime: datetime('datetime', { mode: 'date' }), + datetimeString: datetime('datetimeString', { mode: 'string' }), + time: time('time'), + timestampDate: timestamp('timestamp_date', { mode: 'date' }), + timestampString: timestamp('timestamp_string', { mode: 'string' }), + json: json('json'), + // Vector type - SingleStore specific + embedding: vector('embedding', { dimensions: 128 }), +}); + +// All generators tables ------------------------------- +export const datetimeTable = singlestoreTable('datetime_table', { + datetime: datetime('datetime'), +}); + +// Vector embedding table - SingleStore specific +export const vectorTable = singlestoreTable('vector_table', { + id: int('id').primaryKey().autoincrement(), + embedding: vector('embedding', { dimensions: 1536 }), +}); + +// Document with embeddings table - SingleStore specific +export const documentTable = singlestoreTable('document_table', { + id: int('id').primaryKey().autoincrement(), + title: text('title').notNull(), + content: text('content').notNull(), + metadata: json('metadata'), + embedding: vector('embedding', { dimensions: 768 }), +});