diff --git a/.changeset/hot-paws-count.md b/.changeset/hot-paws-count.md new file mode 100644 index 0000000000..7512bc4f0e --- /dev/null +++ b/.changeset/hot-paws-count.md @@ -0,0 +1,6 @@ +--- +"electric-sql": patch +"@electric-sql/prisma-generator": patch +--- + +Adds client-side support for booleans. diff --git a/clients/typescript/src/client/conversions/datatypes/boolean.ts b/clients/typescript/src/client/conversions/datatypes/boolean.ts new file mode 100644 index 0000000000..a6a8bd6a28 --- /dev/null +++ b/clients/typescript/src/client/conversions/datatypes/boolean.ts @@ -0,0 +1,11 @@ +// Serialises a boolean to a number (0 for false and 1 for true) +export function serialiseBoolean(v: boolean): number { + return v ? 1 : 0 +} + +// Deserialises a SQLite boolean (i.e. 0 or 1) into a boolean value +export function deserialiseBoolean(v: number): boolean { + if (v === 0) return false + else if (v === 1) return true + else throw new Error(`Could not parse boolean. Value is not 0 or 1: ${v}`) +} diff --git a/clients/typescript/src/client/conversions/datatypes/date.ts b/clients/typescript/src/client/conversions/datatypes/date.ts new file mode 100644 index 0000000000..2225cbb16f --- /dev/null +++ b/clients/typescript/src/client/conversions/datatypes/date.ts @@ -0,0 +1,76 @@ +import { PgDateType } from '../types' + +// Serialises a `Date` object into a SQLite compatible date string +export function serialiseDate(v: Date, pgType: PgDateType): string { + switch (pgType) { + case PgDateType.PG_TIMESTAMP: + // Returns local timestamp + return ignoreTimeZone(v).toISOString().replace('T', ' ').replace('Z', '') + + case PgDateType.PG_TIMESTAMPTZ: + // Returns UTC timestamp + return v.toISOString().replace('T', ' ') + + case PgDateType.PG_DATE: + // Returns the local date + return extractDateAndTime(ignoreTimeZone(v)).date + + case PgDateType.PG_TIME: + // Returns the local time + return extractDateAndTime(ignoreTimeZone(v)).time + + case PgDateType.PG_TIMETZ: + // Returns UTC time + return extractDateAndTime(v).time + } +} + +// Deserialises a SQLite compatible date string into a `Date` object +export function deserialiseDate(v: string, pgType: PgDateType): Date { + const parse = (v: any) => { + const millis = Date.parse(v) + if (isNaN(millis)) + throw new Error(`Could not parse date, invalid format: ${v}`) + else return new Date(millis) + } + + switch (pgType) { + case PgDateType.PG_TIMESTAMP: + case PgDateType.PG_TIMESTAMPTZ: + case PgDateType.PG_DATE: + return parse(v) + + case PgDateType.PG_TIME: + // interpret as local time + return parse(`1970-01-01 ${v}`) + + case PgDateType.PG_TIMETZ: + // interpret as UTC time + return parse(`1970-01-01 ${v}+00`) + } +} + +/** + * Corrects the provided `Date` such that + * the current date is set as UTC date. + * e.g. if it is 3PM in GMT+2 then it is 1PM UTC. + * This function would return a date in which it is 3PM UTC. + */ +function ignoreTimeZone(v: Date): Date { + // `v.toISOString` returns the UTC time but we want the time in this timezone + // so we get the timezone offset and subtract it from the current time in order to + // compensate for the timezone correction done by `toISOString` + const offsetInMs = 1000 * 60 * v.getTimezoneOffset() + return new Date(v.getTime() - offsetInMs) +} + +type ExtractedDateTime = { date: string; time: string } +function extractDateAndTime(v: Date): ExtractedDateTime { + const regex = /([0-9-]*)T([0-9:.]*)Z/g + const [_, date, time] = regex.exec(v.toISOString())! as unknown as [ + string, + string, + string + ] + return { date, time } +} diff --git a/clients/typescript/src/client/conversions/input.ts b/clients/typescript/src/client/conversions/input.ts index c78a7ff913..f3c9de07d8 100644 --- a/clients/typescript/src/client/conversions/input.ts +++ b/clients/typescript/src/client/conversions/input.ts @@ -1,8 +1,9 @@ import mapValues from 'lodash.mapvalues' import { FieldName, Fields } from '../model/schema' -import { PgDateType, PgType, fromSqlite, toSqlite } from './sqlite' +import { fromSqlite, toSqlite } from './sqlite' import { InvalidArgumentError } from '../validation/errors/invalidArgumentError' import { mapObject } from '../util/functions' +import { PgDateType, PgType } from './types' export enum Transformation { Js2Sqlite, diff --git a/clients/typescript/src/client/conversions/sqlite.ts b/clients/typescript/src/client/conversions/sqlite.ts index df5da49653..7d505f116d 100644 --- a/clients/typescript/src/client/conversions/sqlite.ts +++ b/clients/typescript/src/client/conversions/sqlite.ts @@ -1,4 +1,7 @@ import { InvalidArgumentError } from '../validation/errors/invalidArgumentError' +import { deserialiseBoolean, serialiseBoolean } from './datatypes/boolean' +import { deserialiseDate, serialiseDate } from './datatypes/date' +import { PgBasicType, PgDateType, PgType } from './types' /** * This module takes care of converting TypeScript values for Postgres-specific types to a SQLite storeable value and back. @@ -8,27 +11,6 @@ import { InvalidArgumentError } from '../validation/errors/invalidArgumentError' * When reading from the SQLite database, the string can be parsed back into a `Date` object. */ -export enum PgBasicType { - PG_BOOL = 'BOOLEAN', - PG_SMALLINT = 'INT2', - PG_INT = 'INT4', - PG_FLOAT = 'FLOAT8', - PG_TEXT = 'TEXT', -} - -/** - * Union type of all Pg types that are represented by a `Date` in JS/TS. - */ -export enum PgDateType { - PG_TIMESTAMP = 'TIMESTAMP', - PG_TIMESTAMPTZ = 'TIMESTAMPTZ', - PG_DATE = 'DATE', - PG_TIME = 'TIME', - PG_TIMETZ = 'TIMETZ', -} - -export type PgType = PgBasicType | PgDateType - export function toSqlite(v: any, pgType: PgType): any { if (v === null) { // don't transform null values @@ -40,6 +22,8 @@ export function toSqlite(v: any, pgType: PgType): any { ) return serialiseDate(v, pgType as PgDateType) + } else if (pgType === PgBasicType.PG_BOOL) { + return serialiseBoolean(v) } else { return v } @@ -52,86 +36,14 @@ export function fromSqlite(v: any, pgType: PgType): any { } else if (isPgDateType(pgType)) { // it's a serialised date return deserialiseDate(v, pgType as PgDateType) + } else if (pgType === PgBasicType.PG_BOOL) { + // it's a serialised boolean + return deserialiseBoolean(v) } else { return v } } -// Serialises a `Date` object into a SQLite compatible date string -function serialiseDate(v: Date, pgType: PgDateType): string { - switch (pgType) { - case PgDateType.PG_TIMESTAMP: - // Returns local timestamp - return ignoreTimeZone(v).toISOString().replace('T', ' ').replace('Z', '') - - case PgDateType.PG_TIMESTAMPTZ: - // Returns UTC timestamp - return v.toISOString().replace('T', ' ') - - case PgDateType.PG_DATE: - // Returns the local date - return extractDateAndTime(ignoreTimeZone(v)).date - - case PgDateType.PG_TIME: - // Returns the local time - return extractDateAndTime(ignoreTimeZone(v)).time - - case PgDateType.PG_TIMETZ: - // Returns UTC time - return extractDateAndTime(v).time - } -} - -// Deserialises a SQLite compatible date string into a `Date` object -function deserialiseDate(v: string, pgType: PgDateType): Date { - switch (pgType) { - case PgDateType.PG_DATE: - case PgDateType.PG_TIMESTAMP: - case PgDateType.PG_TIMESTAMPTZ: - return parseDate(v) - - case PgDateType.PG_TIME: - // interpret as local time - return parseDate(`1970-01-01 ${v}`) - - case PgDateType.PG_TIMETZ: - // interpret as UTC time - return parseDate(`1970-01-01 ${v}+00`) - } -} - -function parseDate(v: string) { - const millis = Date.parse(v) - if (isNaN(millis)) - throw new Error(`Could not parse date, invalid format: ${v}`) - else return new Date(millis) -} - -/** - * Corrects the provided `Date` such that - * the current date is set as UTC date. - * e.g. if it is 3PM in GMT+2 then it is 1PM UTC. - * This function would return a date in which it is 3PM UTC. - */ -function ignoreTimeZone(v: Date): Date { - // `v.toISOString` returns the UTC time but we want the time in this timezone - // so we get the timezone offset and subtract it from the current time in order to - // compensate for the timezone correction done by `toISOString` - const offsetInMs = 1000 * 60 * v.getTimezoneOffset() - return new Date(v.getTime() - offsetInMs) -} - -type ExtractedDateTime = { date: string; time: string } -function extractDateAndTime(v: Date): ExtractedDateTime { - const regex = /([0-9-]*)T([0-9:.]*)Z/g - const [_, date, time] = regex.exec(v.toISOString())! as unknown as [ - string, - string, - string - ] - return { date, time } -} - function isPgDateType(pgType: PgType): boolean { return (Object.values(PgDateType) as Array).includes(pgType) } diff --git a/clients/typescript/src/client/conversions/types.ts b/clients/typescript/src/client/conversions/types.ts new file mode 100644 index 0000000000..fe21577aee --- /dev/null +++ b/clients/typescript/src/client/conversions/types.ts @@ -0,0 +1,28 @@ +export enum PgBasicType { + PG_BOOL = 'BOOL', + PG_INT = 'INT', + PG_INT2 = 'INT2', + PG_INT4 = 'INT4', + PG_INT8 = 'INT8', + PG_INTEGER = 'INTEGER', + PG_REAL = 'REAL', + PG_FLOAT4 = 'FLOAT4', + PG_FLOAT8 = 'FLOAT8', + PG_TEXT = 'TEXT', + PG_VARCHAR = 'VARCHAR', + PG_CHAR = 'CHAR', + PG_UUID = 'UUID', +} + +/** + * Union type of all Pg types that are represented by a `Date` in JS/TS. + */ +export enum PgDateType { + PG_TIMESTAMP = 'TIMESTAMP', + PG_TIMESTAMPTZ = 'TIMESTAMPTZ', + PG_DATE = 'DATE', + PG_TIME = 'TIME', + PG_TIMETZ = 'TIMETZ', +} + +export type PgType = PgBasicType | PgDateType diff --git a/clients/typescript/src/client/model/schema.ts b/clients/typescript/src/client/model/schema.ts index e208cd098a..2166d996ec 100644 --- a/clients/typescript/src/client/model/schema.ts +++ b/clients/typescript/src/client/model/schema.ts @@ -9,7 +9,7 @@ import { DeleteInput, DeleteManyInput } from '../input/deleteInput' import { HKT } from '../util/hkt' import groupBy from 'lodash.groupby' import { Migration } from '../../migrators' -import { PgType } from '../conversions/sqlite' +import { PgType } from '../conversions/types' export type Arity = 'one' | 'many' @@ -166,6 +166,10 @@ export class DbSchema { return obj } + hasTable(table: TableName): boolean { + return Object.keys(this.extendedTables).includes(table) + } + getTableDescription( table: TableName ): ExtendedTableSchema { diff --git a/clients/typescript/src/electric/index.ts b/clients/typescript/src/electric/index.ts index 3e5927b71a..2a755395d4 100644 --- a/clients/typescript/src/electric/index.ts +++ b/clients/typescript/src/electric/index.ts @@ -61,6 +61,7 @@ export const electrify = async >( const satellite = await registry.ensureStarted( dbName, + dbDescription, adapter, migrator, notifier, diff --git a/clients/typescript/src/satellite/client.ts b/clients/typescript/src/satellite/client.ts index 967bccba04..58bbcc0f39 100644 --- a/clients/typescript/src/satellite/client.ts +++ b/clients/typescript/src/satellite/client.ts @@ -88,6 +88,8 @@ import { SubscriptionsDataCache } from './shapes/cache' import { setMaskBit, getMaskBit } from '../util/bitmaskHelpers' import { RPC, rpcRespond, withRpcRequestLogging } from './RPC' import { Mutex } from 'async-mutex' +import { DbSchema } from '../client/model' +import { PgBasicType, PgDateType, PgType } from '../client/conversions/types' type IncomingHandler = (msg: any) => void @@ -120,6 +122,8 @@ export class SatelliteClient extends EventEmitter implements Client { private incomingMutex: Mutex = new Mutex() private allowedMutexedRpcResponses: Array = [] + private dbDescription: DbSchema + private handlerForMessageType: { [k: string]: IncomingHandler } = Object.fromEntries( Object.entries({ @@ -143,6 +147,7 @@ export class SatelliteClient extends EventEmitter implements Client { constructor( _dbName: string, + dbDescription: DbSchema, socketFactory: SocketFactory, _notifier: Notifier, opts: SatelliteClientOpts @@ -154,8 +159,9 @@ export class SatelliteClient extends EventEmitter implements Client { this.inbound = this.resetReplication() this.outbound = this.resetReplication() + this.dbDescription = dbDescription - this.subscriptionsDataCache = new SubscriptionsDataCache() + this.subscriptionsDataCache = new SubscriptionsDataCache(dbDescription) this.rpcClient = new RPC( this.sendMessage.bind(this), this.opts.timeout, @@ -503,10 +509,10 @@ export class SatelliteClient extends EventEmitter implements Client { const relation = this.outbound.relations.get(change.relation.id)! const tags = change.tags if (change.oldRecord) { - oldRecord = serializeRow(change.oldRecord, relation) + oldRecord = serializeRow(change.oldRecord, relation, this.dbDescription) } if (change.record) { - record = serializeRow(change.record, relation) + record = serializeRow(change.record, relation, this.dbDescription) } switch (change.type) { case DataChangeType.DELETE: @@ -867,7 +873,7 @@ export class SatelliteClient extends EventEmitter implements Client { const change = { relation: rel, type: DataChangeType.INSERT, - record: deserializeRow(op.insert.rowData!, rel), + record: deserializeRow(op.insert.rowData!, rel, this.dbDescription), tags: op.insert.tags, } @@ -887,8 +893,12 @@ export class SatelliteClient extends EventEmitter implements Client { const change = { relation: rel, type: DataChangeType.UPDATE, - record: deserializeRow(op.update.rowData!, rel), - oldRecord: deserializeRow(op.update.oldRowData, rel), + record: deserializeRow(op.update.rowData!, rel, this.dbDescription), + oldRecord: deserializeRow( + op.update.oldRowData, + rel, + this.dbDescription + ), tags: op.update.tags, } @@ -908,7 +918,11 @@ export class SatelliteClient extends EventEmitter implements Client { const change = { relation: rel, type: DataChangeType.DELETE, - oldRecord: deserializeRow(op.delete.oldRowData!, rel), + oldRecord: deserializeRow( + op.delete.oldRowData!, + rel, + this.dbDescription + ), tags: op.delete.tags, } @@ -968,7 +982,43 @@ export class SatelliteClient extends EventEmitter implements Client { } } -export function serializeRow(rec: Record, relation: Relation): SatOpRow { +/** + * Fetches the PG type of the given column in the given table. + * @param dbDescription Database description object + * @param table Name of the table + * @param column Name of the column + * @returns The PG type of the column + */ +function getColumnType( + dbDescription: DbSchema, + table: string, + column: RelationColumn +): PgType { + if ( + dbDescription.hasTable(table) && + dbDescription.getFields(table).has(column.name) + ) { + // The table and column are known in the DB description + return dbDescription.getFields(table).get(column.name)! + } else { + // The table or column is not known. + // There must have been a migration that added it to the DB while the app was running. + // i.e., it was not known at the time the Electric client for this app was generated + // so it is not present in the bundled DB description. + // Thus, we return the column type that is stored in the relation. + // Note that it is fine to fetch the column type from the relation + // because it was received at runtime and thus will have the PG type + // (which would not be the case for bundled relations fetched + // from the endpoint because the endpoint maps PG types to SQLite types). + return column.type.toUpperCase() as PgType + } +} + +export function serializeRow( + rec: Record, + relation: Relation, + dbDescription: DbSchema +): SatOpRow { let recordNumColumn = 0 const recordNullBitMask = new Uint8Array( calculateNumBytes(relation.columns.length) @@ -976,7 +1026,8 @@ export function serializeRow(rec: Record, relation: Relation): SatOpRow { const recordValues = relation!.columns.reduce( (acc: Uint8Array[], c: RelationColumn) => { if (rec[c.name] != null) { - acc.push(serializeColumnData(rec[c.name]!, c)) + const pgColumnType = getColumnType(dbDescription, relation.table, c) + acc.push(serializeColumnData(rec[c.name]!, pgColumnType)) } else { acc.push(serializeNullData()) setMaskBit(recordNullBitMask, recordNumColumn) @@ -994,18 +1045,20 @@ export function serializeRow(rec: Record, relation: Relation): SatOpRow { export function deserializeRow( row: SatOpRow | undefined, - relation: Relation + relation: Relation, + dbDescription: DbSchema ): Record | undefined { if (row == undefined) { return undefined } return Object.fromEntries( - relation!.columns.map((c, i) => { + relation.columns.map((c, i) => { let value if (getMaskBit(row.nullsBitmask, i) == 1) { value = null } else { - value = deserializeColumnData(row.values[i], c) + const pgColumnType = getColumnType(dbDescription, relation.table, c) + value = deserializeColumnData(row.values[i], pgColumnType) } return [c.name, value] }) @@ -1023,53 +1076,53 @@ function calculateNumBytes(column_num: number): number { function deserializeColumnData( column: Uint8Array, - columnInfo: RelationColumn + columnType: PgType ): string | number { - const columnType = columnInfo.type.toUpperCase() switch (columnType) { - case 'CHAR': - case 'DATE': - case 'TEXT': - case 'TIME': - case 'TIMESTAMP': - case 'TIMESTAMPTZ': - case 'UUID': - case 'VARCHAR': + case PgBasicType.PG_CHAR: + case PgBasicType.PG_TEXT: + case PgBasicType.PG_UUID: + case PgBasicType.PG_VARCHAR: + case PgDateType.PG_DATE: + case PgDateType.PG_TIME: + case PgDateType.PG_TIMESTAMP: + case PgDateType.PG_TIMESTAMPTZ: return typeDecoder.text(column) - case 'BOOL': + case PgBasicType.PG_BOOL: return typeDecoder.bool(column) - case 'FLOAT4': - case 'FLOAT8': - case 'INT': - case 'INT2': - case 'INT4': - case 'INT8': - case 'INTEGER': - case 'REAL': + case PgBasicType.PG_REAL: + case PgBasicType.PG_FLOAT4: + case PgBasicType.PG_FLOAT8: + case PgBasicType.PG_INT: + case PgBasicType.PG_INT2: + case PgBasicType.PG_INT4: + case PgBasicType.PG_INT8: + case PgBasicType.PG_INTEGER: return Number(typeDecoder.text(column)) - case 'TIMETZ': + case PgDateType.PG_TIMETZ: return typeDecoder.timetz(column) + default: + // should not occur + throw new SatelliteError( + SatelliteErrorCode.UNKNOWN_DATA_TYPE, + `can't deserialize ${columnType}` + ) } - throw new SatelliteError( - SatelliteErrorCode.UNKNOWN_DATA_TYPE, - `can't deserialize ${columnInfo.type}` - ) } // All values serialized as textual representation function serializeColumnData( columnValue: string | number, - columnInfo: RelationColumn + columnType: PgType ): Uint8Array { - const columnType = columnInfo.type.toUpperCase() switch (columnType) { - case 'BOOL': + case PgBasicType.PG_BOOL: return typeEncoder.bool(columnValue as number) - case 'REAL': - case 'FLOAT4': - case 'FLOAT8': + case PgBasicType.PG_REAL: + case PgBasicType.PG_FLOAT4: + case PgBasicType.PG_FLOAT8: return typeEncoder.real(columnValue as number) - case 'TIMETZ': + case PgDateType.PG_TIMETZ: return typeEncoder.timetz(columnValue as string) default: return typeEncoder.text(columnValue as string) diff --git a/clients/typescript/src/satellite/index.ts b/clients/typescript/src/satellite/index.ts index ecb6657b59..3ff6fa45de 100644 --- a/clients/typescript/src/satellite/index.ts +++ b/clients/typescript/src/satellite/index.ts @@ -25,6 +25,7 @@ import { UnsubscribeResponse, } from './shapes/types' import { ShapeSubscription } from './process' +import { DbSchema } from '../client/model/schema' export { SatelliteProcess } from './process' export { GlobalRegistry, globalRegistry } from './registry' @@ -34,6 +35,7 @@ export type { ShapeSubscription } from './process' export interface Registry { ensureStarted( dbName: DbName, + dbDescription: DbSchema, adapter: DatabaseAdapter, migrator: Migrator, notifier: Notifier, diff --git a/clients/typescript/src/satellite/mock.ts b/clients/typescript/src/satellite/mock.ts index ef0aeaab42..a28e9a555e 100644 --- a/clients/typescript/src/satellite/mock.ts +++ b/clients/typescript/src/satellite/mock.ts @@ -46,6 +46,7 @@ import { SatSubsDataError_ShapeReqError_Code, } from '../_generated/protocol/satellite' import { ShapeSubscription } from './process' +import { DbSchema } from '../client/model/schema' export const MOCK_BEHIND_WINDOW_LSN = 42 export const MOCK_INTERNAL_ERROR = 27 @@ -100,6 +101,7 @@ export class MockSatelliteProcess implements Satellite { export class MockRegistry extends BaseRegistry { async startProcess( dbName: DbName, + _dbDescription: DbSchema, adapter: DatabaseAdapter, migrator: Migrator, notifier: Notifier, diff --git a/clients/typescript/src/satellite/process.ts b/clients/typescript/src/satellite/process.ts index fb861fa309..079e6b4c6e 100644 --- a/clients/typescript/src/satellite/process.ts +++ b/clients/typescript/src/satellite/process.ts @@ -72,6 +72,7 @@ import { import { backOff } from 'exponential-backoff' import { chunkBy } from '../util' import { isFatal, isOutOfSyncError, isThrowable, wrapFatalError } from './error' +import { inferRelationsFromSQLite } from '../util/relations' type ChangeAccumulator = { [key: string]: Change @@ -1355,63 +1356,8 @@ export class SatelliteProcess implements Satellite { return clientId } - private async _getLocalTableNames(): Promise<{ name: string }[]> { - const notIn = [ - this.opts.metaTable.tablename.toString(), - this.opts.migrationsTable.tablename.toString(), - this.opts.oplogTable.tablename.toString(), - this.opts.triggersTable.tablename.toString(), - this.opts.shadowTable.tablename.toString(), - 'sqlite_schema', - 'sqlite_sequence', - 'sqlite_temp_schema', - ] - - const tables = ` - SELECT name FROM sqlite_master - WHERE type = 'table' - AND name NOT IN (${notIn.map(() => '?').join(',')}) - ` - return (await this.adapter.query({ sql: tables, args: notIn })) as { - name: string - }[] - } - - // Fetch primary keys from local store and use them to identify incoming ops. - // TODO: Improve this code once with Migrator and consider simplifying oplog. private async _getLocalRelations(): Promise<{ [k: string]: Relation }> { - const tableNames = await this._getLocalTableNames() - const relations: RelationsCache = {} - - let id = 0 - const schema = 'public' // TODO - for (const table of tableNames) { - const tableName = table.name - const sql = 'SELECT * FROM pragma_table_info(?)' - const args = [tableName] - const columnsForTable = await this.adapter.query({ sql, args }) - if (columnsForTable.length == 0) { - continue - } - const relation: Relation = { - id: id++, - schema: schema, - table: tableName, - tableType: SatRelation_RelationType.TABLE, - columns: [], - } - for (const c of columnsForTable) { - relation.columns.push({ - name: c.name!.toString(), - type: c.type!.toString(), - isNullable: Boolean(!c.notnull!.valueOf()), - primaryKey: Boolean(c.pk!.valueOf()), - }) - } - relations[`${tableName}`] = relation - } - - return Promise.resolve(relations) + return inferRelationsFromSQLite(this.adapter, this.opts) } private _generateTag(timestamp: Date): string { diff --git a/clients/typescript/src/satellite/registry.ts b/clients/typescript/src/satellite/registry.ts index a9446bf6f1..eb8408dda9 100644 --- a/clients/typescript/src/satellite/registry.ts +++ b/clients/typescript/src/satellite/registry.ts @@ -16,6 +16,7 @@ import { import { SatelliteProcess } from './process' import { SocketFactory } from '../sockets' import { SatelliteClient } from './client' +import { DbSchema } from '../client/model' export abstract class BaseRegistry implements Registry { satellites: { @@ -37,6 +38,7 @@ export abstract class BaseRegistry implements Registry { abstract startProcess( dbName: DbName, + dbDescription: DbSchema, adapter: DatabaseAdapter, migrator: Migrator, notifier: Notifier, @@ -47,6 +49,7 @@ export abstract class BaseRegistry implements Registry { async ensureStarted( dbName: DbName, + dbDescription: DbSchema, adapter: DatabaseAdapter, migrator: Migrator, notifier: Notifier, @@ -63,6 +66,7 @@ export abstract class BaseRegistry implements Registry { return stopping.then(() => this.ensureStarted( dbName, + dbDescription, adapter, migrator, notifier, @@ -97,6 +101,7 @@ export abstract class BaseRegistry implements Registry { // Otherwise we need to fire it up! const startingPromise = this.startProcess( dbName, + dbDescription, adapter, migrator, notifier, @@ -182,6 +187,7 @@ export abstract class BaseRegistry implements Registry { export class GlobalRegistry extends BaseRegistry { async startProcess( dbName: DbName, + dbDescription: DbSchema, adapter: DatabaseAdapter, migrator: Migrator, notifier: Notifier, @@ -202,6 +208,7 @@ export class GlobalRegistry extends BaseRegistry { const client = new SatelliteClient( dbName, + dbDescription, socketFactory, notifier, satelliteClientOpts diff --git a/clients/typescript/src/satellite/shapes/cache.ts b/clients/typescript/src/satellite/shapes/cache.ts index f9106517d0..579c272450 100644 --- a/clients/typescript/src/satellite/shapes/cache.ts +++ b/clients/typescript/src/satellite/shapes/cache.ts @@ -20,6 +20,7 @@ import { SUBSCRIPTION_ERROR, SubscriptionData, } from './types' +import { DbSchema } from '../../client/model/schema' type SubscriptionId = string type RequestId = string @@ -36,12 +37,14 @@ export class SubscriptionsDataCache extends EventEmitter { remainingShapes: Set currentShapeRequestId?: RequestId inDelivery?: SubscriptionDataInternal + dbDescription: DbSchema - constructor() { + constructor(dbDescription: DbSchema) { super() this.requestedSubscriptions = {} this.remainingShapes = new Set() + this.dbDescription = dbDescription } isDelivering(): boolean { @@ -276,7 +279,7 @@ export class SubscriptionsDataCache extends EventEmitter { ) } - const record = deserializeRow(rowData, relation) + const record = deserializeRow(rowData, relation, this.dbDescription) if (!record) { this.internalError( diff --git a/clients/typescript/src/util/relations.ts b/clients/typescript/src/util/relations.ts new file mode 100644 index 0000000000..20ff615b7a --- /dev/null +++ b/clients/typescript/src/util/relations.ts @@ -0,0 +1,67 @@ +import { SatRelation_RelationType } from '../_generated/protocol/satellite' +import { DatabaseAdapter } from '../electric/adapter' +import { SatelliteOpts } from '../satellite/config' +import { Relation, RelationsCache } from './types' + +// TODO: Improve this code once with Migrator and consider simplifying oplog. +export async function inferRelationsFromSQLite( + adapter: DatabaseAdapter, + opts: SatelliteOpts +): Promise<{ [k: string]: Relation }> { + const tableNames = await _getLocalTableNames(adapter, opts) + const relations: RelationsCache = {} + + let id = 0 + const schema = 'public' // TODO + for (const table of tableNames) { + const tableName = table.name + const sql = 'SELECT * FROM pragma_table_info(?)' + const args = [tableName] + const columnsForTable = await adapter.query({ sql, args }) + if (columnsForTable.length == 0) { + continue + } + const relation: Relation = { + id: id++, + schema: schema, + table: tableName, + tableType: SatRelation_RelationType.TABLE, + columns: [], + } + for (const c of columnsForTable) { + relation.columns.push({ + name: c.name!.toString(), + type: c.type!.toString(), + isNullable: Boolean(!c.notnull!.valueOf()), + primaryKey: Boolean(c.pk!.valueOf()), + }) + } + relations[`${tableName}`] = relation + } + + return relations +} + +async function _getLocalTableNames( + adapter: DatabaseAdapter, + opts: SatelliteOpts +): Promise<{ name: string }[]> { + const notIn = [ + opts.metaTable.tablename.toString(), + opts.migrationsTable.tablename.toString(), + opts.oplogTable.tablename.toString(), + opts.triggersTable.tablename.toString(), + opts.shadowTable.tablename.toString(), + 'sqlite_schema', + 'sqlite_sequence', + 'sqlite_temp_schema', + ] + + const tables = ` + SELECT name FROM sqlite_master + WHERE type = 'table' + AND name NOT IN (${notIn.map(() => '?').join(',')}) + ` + const rows = await adapter.query({ sql: tables, args: notIn }) + return rows as Array<{ name: string }> +} diff --git a/clients/typescript/test/client/conversions/input.test.ts b/clients/typescript/test/client/conversions/input.test.ts index 2071358707..5049366912 100644 --- a/clients/typescript/test/client/conversions/input.test.ts +++ b/clients/typescript/test/client/conversions/input.test.ts @@ -31,7 +31,7 @@ await tbl.sync() function setupDB() { db.exec('DROP TABLE IF EXISTS DataTypes') db.exec( - "CREATE TABLE DataTypes('id' int PRIMARY KEY, 'date' varchar, 'time' varchar, 'timetz' varchar, 'timestamp' varchar, 'timestamptz' varchar, 'relatedId' int);" + "CREATE TABLE DataTypes('id' int PRIMARY KEY, 'date' varchar, 'time' varchar, 'timetz' varchar, 'timestamp' varchar, 'timestamptz' varchar, 'bool' int, 'relatedId' int);" ) db.exec('DROP TABLE IF EXISTS Dummy') @@ -214,6 +214,7 @@ const dateNulls = { timetz: null, timestamp: null, timestamptz: null, + bool: null, } const nulls = { diff --git a/clients/typescript/test/client/conversions/sqlite.test.ts b/clients/typescript/test/client/conversions/sqlite.test.ts index 3647211ef1..961737d255 100644 --- a/clients/typescript/test/client/conversions/sqlite.test.ts +++ b/clients/typescript/test/client/conversions/sqlite.test.ts @@ -30,7 +30,7 @@ await tbl.sync() function setupDB() { db.exec('DROP TABLE IF EXISTS DataTypes') db.exec( - "CREATE TABLE DataTypes('id' int PRIMARY KEY, 'date' varchar, 'time' varchar, 'timetz' varchar, 'timestamp' varchar, 'timestamptz' varchar, 'relatedId' int);" + "CREATE TABLE DataTypes('id' int PRIMARY KEY, 'date' varchar, 'time' varchar, 'timetz' varchar, 'timestamp' varchar, 'timestamptz' varchar, 'bool' int, 'relatedId' int);" ) } @@ -152,3 +152,28 @@ test.serial('timestamptz is converted correctly to SQLite', async (t) => { }) t.is(rawRes2[0].timestamptz, '2023-08-07 15:28:35.421Z') }) + +test.serial('booleans are converted correctly to SQLite', async (t) => { + await tbl.createMany({ + data: [ + { + id: 1, + bool: true, + }, + { + id: 2, + bool: false, + }, + ], + }) + + const rawRes = await electric.db.raw({ + sql: 'SELECT id, bool FROM DataTypes ORDER BY id ASC', + args: [], + }) + + t.deepEqual(rawRes, [ + { id: 1, bool: 1 }, + { id: 2, bool: 0 }, + ]) +}) diff --git a/clients/typescript/test/client/generated/client/index-browser.js b/clients/typescript/test/client/generated/client/index-browser.js index 5c08f2c703..243c6f9ea1 100644 --- a/clients/typescript/test/client/generated/client/index-browser.js +++ b/clients/typescript/test/client/generated/client/index-browser.js @@ -93,6 +93,7 @@ exports.Prisma.DataTypesScalarFieldEnum = { timetz: 'timetz', timestamp: 'timestamp', timestamptz: 'timestamptz', + bool: 'bool', relatedId: 'relatedId' }; diff --git a/clients/typescript/test/client/generated/client/index.d.ts b/clients/typescript/test/client/generated/client/index.d.ts index 3f5f6f98b0..52cf373049 100644 --- a/clients/typescript/test/client/generated/client/index.d.ts +++ b/clients/typescript/test/client/generated/client/index.d.ts @@ -63,6 +63,7 @@ export type DataTypes = { timetz: Date | null timestamp: Date | null timestamptz: Date | null + bool: boolean | null relatedId: number | null } @@ -4797,6 +4798,7 @@ export namespace Prisma { timetz: Date | null timestamp: Date | null timestamptz: Date | null + bool: boolean | null relatedId: number | null } @@ -4807,6 +4809,7 @@ export namespace Prisma { timetz: Date | null timestamp: Date | null timestamptz: Date | null + bool: boolean | null relatedId: number | null } @@ -4817,6 +4820,7 @@ export namespace Prisma { timetz: number timestamp: number timestamptz: number + bool: number relatedId: number _all: number } @@ -4839,6 +4843,7 @@ export namespace Prisma { timetz?: true timestamp?: true timestamptz?: true + bool?: true relatedId?: true } @@ -4849,6 +4854,7 @@ export namespace Prisma { timetz?: true timestamp?: true timestamptz?: true + bool?: true relatedId?: true } @@ -4859,6 +4865,7 @@ export namespace Prisma { timetz?: true timestamp?: true timestamptz?: true + bool?: true relatedId?: true _all?: true } @@ -4957,6 +4964,7 @@ export namespace Prisma { timetz: Date | null timestamp: Date | null timestamptz: Date | null + bool: boolean | null relatedId: number | null _count: DataTypesCountAggregateOutputType | null _avg: DataTypesAvgAggregateOutputType | null @@ -4986,6 +4994,7 @@ export namespace Prisma { timetz?: boolean timestamp?: boolean timestamptz?: boolean + bool?: boolean relatedId?: boolean related?: boolean | DummyArgs } @@ -6729,6 +6738,7 @@ export namespace Prisma { timetz: 'timetz', timestamp: 'timestamp', timestamptz: 'timestamptz', + bool: 'bool', relatedId: 'relatedId' }; @@ -6986,6 +6996,7 @@ export namespace Prisma { timetz?: DateTimeNullableFilter | Date | string | null timestamp?: DateTimeNullableFilter | Date | string | null timestamptz?: DateTimeNullableFilter | Date | string | null + bool?: BoolNullableFilter | boolean | null relatedId?: IntNullableFilter | number | null related?: XOR | null } @@ -6997,6 +7008,7 @@ export namespace Prisma { timetz?: SortOrder timestamp?: SortOrder timestamptz?: SortOrder + bool?: SortOrder relatedId?: SortOrder related?: DummyOrderByWithRelationInput } @@ -7013,6 +7025,7 @@ export namespace Prisma { timetz?: SortOrder timestamp?: SortOrder timestamptz?: SortOrder + bool?: SortOrder relatedId?: SortOrder _count?: DataTypesCountOrderByAggregateInput _avg?: DataTypesAvgOrderByAggregateInput @@ -7031,6 +7044,7 @@ export namespace Prisma { timetz?: DateTimeNullableWithAggregatesFilter | Date | string | null timestamp?: DateTimeNullableWithAggregatesFilter | Date | string | null timestamptz?: DateTimeNullableWithAggregatesFilter | Date | string | null + bool?: BoolNullableWithAggregatesFilter | boolean | null relatedId?: IntNullableWithAggregatesFilter | number | null } @@ -7252,6 +7266,7 @@ export namespace Prisma { timetz?: Date | string | null timestamp?: Date | string | null timestamptz?: Date | string | null + bool?: boolean | null related?: DummyCreateNestedOneWithoutDatatypeInput } @@ -7262,6 +7277,7 @@ export namespace Prisma { timetz?: Date | string | null timestamp?: Date | string | null timestamptz?: Date | string | null + bool?: boolean | null relatedId?: number | null } @@ -7272,6 +7288,7 @@ export namespace Prisma { timetz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamp?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamptz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bool?: NullableBoolFieldUpdateOperationsInput | boolean | null related?: DummyUpdateOneWithoutDatatypeNestedInput } @@ -7282,6 +7299,7 @@ export namespace Prisma { timetz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamp?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamptz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bool?: NullableBoolFieldUpdateOperationsInput | boolean | null relatedId?: NullableIntFieldUpdateOperationsInput | number | null } @@ -7292,6 +7310,7 @@ export namespace Prisma { timetz?: Date | string | null timestamp?: Date | string | null timestamptz?: Date | string | null + bool?: boolean | null relatedId?: number | null } @@ -7302,6 +7321,7 @@ export namespace Prisma { timetz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamp?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamptz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bool?: NullableBoolFieldUpdateOperationsInput | boolean | null } export type DataTypesUncheckedUpdateManyInput = { @@ -7311,6 +7331,7 @@ export namespace Prisma { timetz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamp?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamptz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bool?: NullableBoolFieldUpdateOperationsInput | boolean | null relatedId?: NullableIntFieldUpdateOperationsInput | number | null } @@ -7614,6 +7635,11 @@ export namespace Prisma { not?: NestedDateTimeNullableFilter | Date | string | null } + export type BoolNullableFilter = { + equals?: boolean | null + not?: NestedBoolNullableFilter | boolean | null + } + export type DummyRelationFilter = { is?: DummyWhereInput | null isNot?: DummyWhereInput | null @@ -7626,6 +7652,7 @@ export namespace Prisma { timetz?: SortOrder timestamp?: SortOrder timestamptz?: SortOrder + bool?: SortOrder relatedId?: SortOrder } @@ -7641,6 +7668,7 @@ export namespace Prisma { timetz?: SortOrder timestamp?: SortOrder timestamptz?: SortOrder + bool?: SortOrder relatedId?: SortOrder } @@ -7651,6 +7679,7 @@ export namespace Prisma { timetz?: SortOrder timestamp?: SortOrder timestamptz?: SortOrder + bool?: SortOrder relatedId?: SortOrder } @@ -7673,6 +7702,14 @@ export namespace Prisma { _max?: NestedDateTimeNullableFilter } + export type BoolNullableWithAggregatesFilter = { + equals?: boolean | null + not?: NestedBoolNullableWithAggregatesFilter | boolean | null + _count?: NestedIntNullableFilter + _min?: NestedBoolNullableFilter + _max?: NestedBoolNullableFilter + } + export type DataTypesListRelationFilter = { every?: DataTypesWhereInput some?: DataTypesWhereInput @@ -7846,6 +7883,10 @@ export namespace Prisma { set?: Date | string | null } + export type NullableBoolFieldUpdateOperationsInput = { + set?: boolean | null + } + export type DummyUpdateOneWithoutDatatypeNestedInput = { create?: XOR connectOrCreate?: DummyCreateOrConnectWithoutDatatypeInput @@ -8047,6 +8088,11 @@ export namespace Prisma { not?: NestedDateTimeNullableFilter | Date | string | null } + export type NestedBoolNullableFilter = { + equals?: boolean | null + not?: NestedBoolNullableFilter | boolean | null + } + export type NestedDateTimeNullableWithAggregatesFilter = { equals?: Date | string | null in?: Enumerable | Enumerable | Date | string | null @@ -8061,6 +8107,14 @@ export namespace Prisma { _max?: NestedDateTimeNullableFilter } + export type NestedBoolNullableWithAggregatesFilter = { + equals?: boolean | null + not?: NestedBoolNullableWithAggregatesFilter | boolean | null + _count?: NestedIntNullableFilter + _min?: NestedBoolNullableFilter + _max?: NestedBoolNullableFilter + } + export type PostCreateWithoutAuthorInput = { id: number title: string @@ -8247,6 +8301,7 @@ export namespace Prisma { timetz?: Date | string | null timestamp?: Date | string | null timestamptz?: Date | string | null + bool?: boolean | null } export type DataTypesUncheckedCreateWithoutRelatedInput = { @@ -8256,6 +8311,7 @@ export namespace Prisma { timetz?: Date | string | null timestamp?: Date | string | null timestamptz?: Date | string | null + bool?: boolean | null } export type DataTypesCreateOrConnectWithoutRelatedInput = { @@ -8294,6 +8350,7 @@ export namespace Prisma { timetz?: DateTimeNullableFilter | Date | string | null timestamp?: DateTimeNullableFilter | Date | string | null timestamptz?: DateTimeNullableFilter | Date | string | null + bool?: BoolNullableFilter | boolean | null relatedId?: IntNullableFilter | number | null } @@ -8332,6 +8389,7 @@ export namespace Prisma { timetz?: Date | string | null timestamp?: Date | string | null timestamptz?: Date | string | null + bool?: boolean | null } export type DataTypesUpdateWithoutRelatedInput = { @@ -8341,6 +8399,7 @@ export namespace Prisma { timetz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamp?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamptz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bool?: NullableBoolFieldUpdateOperationsInput | boolean | null } export type DataTypesUncheckedUpdateWithoutRelatedInput = { @@ -8350,6 +8409,7 @@ export namespace Prisma { timetz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamp?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamptz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bool?: NullableBoolFieldUpdateOperationsInput | boolean | null } export type DataTypesUncheckedUpdateManyWithoutDatatypeInput = { @@ -8359,6 +8419,7 @@ export namespace Prisma { timetz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamp?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null timestamptz?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + bool?: NullableBoolFieldUpdateOperationsInput | boolean | null } diff --git a/clients/typescript/test/client/generated/client/index.js b/clients/typescript/test/client/generated/client/index.js index 39c941231f..c4f91b1f42 100644 --- a/clients/typescript/test/client/generated/client/index.js +++ b/clients/typescript/test/client/generated/client/index.js @@ -82,6 +82,7 @@ exports.Prisma.DataTypesScalarFieldEnum = { timetz: 'timetz', timestamp: 'timestamp', timestamptz: 'timestamptz', + bool: 'bool', relatedId: 'relatedId' }; @@ -189,7 +190,7 @@ if (!fs.existsSync(path.join(__dirname, 'schema.prisma'))) { config.isBundled = true } -config.runtimeDataModel = JSON.parse("{\"models\":{\"Items\":{\"dbName\":null,\"fields\":[{\"name\":\"value\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"nbr\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"User\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"name\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"posts\",\"kind\":\"object\",\"isList\":true,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Post\",\"relationName\":\"PostToUser\",\"relationFromFields\":[],\"relationToFields\":[],\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"profile\",\"kind\":\"object\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Profile\",\"relationName\":\"ProfileToUser\",\"relationFromFields\":[],\"relationToFields\":[],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"Post\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"title\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":true,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"contents\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"nbr\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"authorId\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":true,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"author\",\"kind\":\"object\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"User\",\"relationName\":\"PostToUser\",\"relationFromFields\":[\"authorId\"],\"relationToFields\":[\"id\"],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"Profile\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"bio\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"userId\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":true,\"isId\":false,\"isReadOnly\":true,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"user\",\"kind\":\"object\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"User\",\"relationName\":\"ProfileToUser\",\"relationFromFields\":[\"userId\"],\"relationToFields\":[\"id\"],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"DataTypes\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"date\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"time\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"timetz\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"timestamp\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":true,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"timestamptz\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"relatedId\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":true,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"related\",\"kind\":\"object\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Dummy\",\"relationName\":\"DataTypesToDummy\",\"relationFromFields\":[\"relatedId\"],\"relationToFields\":[\"id\"],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"Dummy\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"timestamp\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"datatype\",\"kind\":\"object\",\"isList\":true,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DataTypes\",\"relationName\":\"DataTypesToDummy\",\"relationFromFields\":[],\"relationToFields\":[],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"Items\":{\"dbName\":null,\"fields\":[{\"name\":\"value\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"nbr\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"User\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"name\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"posts\",\"kind\":\"object\",\"isList\":true,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Post\",\"relationName\":\"PostToUser\",\"relationFromFields\":[],\"relationToFields\":[],\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"profile\",\"kind\":\"object\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Profile\",\"relationName\":\"ProfileToUser\",\"relationFromFields\":[],\"relationToFields\":[],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"Post\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"title\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":true,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"contents\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"nbr\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"authorId\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":true,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"author\",\"kind\":\"object\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"User\",\"relationName\":\"PostToUser\",\"relationFromFields\":[\"authorId\"],\"relationToFields\":[\"id\"],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"Profile\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"bio\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"String\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"userId\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":true,\"isId\":false,\"isReadOnly\":true,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"user\",\"kind\":\"object\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"User\",\"relationName\":\"ProfileToUser\",\"relationFromFields\":[\"userId\"],\"relationToFields\":[\"id\"],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"DataTypes\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"date\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"time\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"timetz\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"timestamp\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":true,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"timestamptz\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"bool\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Boolean\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"relatedId\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":true,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"related\",\"kind\":\"object\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Dummy\",\"relationName\":\"DataTypesToDummy\",\"relationFromFields\":[\"relatedId\"],\"relationToFields\":[\"id\"],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false},\"Dummy\":{\"dbName\":null,\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":true,\"isUnique\":false,\"isId\":true,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"Int\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"timestamp\",\"kind\":\"scalar\",\"isList\":false,\"isRequired\":false,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DateTime\",\"isGenerated\":false,\"isUpdatedAt\":false},{\"name\":\"datatype\",\"kind\":\"object\",\"isList\":true,\"isRequired\":true,\"isUnique\":false,\"isId\":false,\"isReadOnly\":false,\"hasDefaultValue\":false,\"type\":\"DataTypes\",\"relationName\":\"DataTypesToDummy\",\"relationFromFields\":[],\"relationToFields\":[],\"isGenerated\":false,\"isUpdatedAt\":false}],\"primaryKey\":null,\"uniqueFields\":[],\"uniqueIndexes\":[],\"isGenerated\":false}},\"enums\":{},\"types\":{}}") defineDmmfProperty(exports.Prisma, config.runtimeDataModel) diff --git a/clients/typescript/test/client/generated/client/schema.prisma b/clients/typescript/test/client/generated/client/schema.prisma index eadab9739d..46b439174a 100644 --- a/clients/typescript/test/client/generated/client/schema.prisma +++ b/clients/typescript/test/client/generated/client/schema.prisma @@ -50,6 +50,7 @@ model DataTypes { timetz DateTime? @db.Timetz(3) timestamp DateTime? @unique @db.Timestamp(3) timestamptz DateTime? @db.Timestamptz(3) + bool Boolean? relatedId Int? related Dummy? @relation(fields: [relatedId], references: [id]) } diff --git a/clients/typescript/test/client/generated/index.ts b/clients/typescript/test/client/generated/index.ts index 0a53e3ba0e..353654a77a 100644 --- a/clients/typescript/test/client/generated/index.ts +++ b/clients/typescript/test/client/generated/index.ts @@ -17,7 +17,7 @@ import { // ENUMS ///////////////////////////////////////// -export const DataTypesScalarFieldEnumSchema = z.enum(['id','date','time','timetz','timestamp','timestamptz','relatedId']); +export const DataTypesScalarFieldEnumSchema = z.enum(['id','date','time','timetz','timestamp','timestamptz','bool','relatedId']); export const DummyScalarFieldEnumSchema = z.enum(['id','timestamp']); @@ -97,6 +97,7 @@ export const DataTypesSchema = z.object({ timetz: z.coerce.date().nullish(), timestamp: z.coerce.date().nullish(), timestamptz: z.coerce.date().nullish(), + bool: z.boolean().nullish(), relatedId: z.number().int().nullish(), }) @@ -214,6 +215,7 @@ export const DataTypesSelectSchema: z.ZodType = z.object timetz: z.boolean().optional(), timestamp: z.boolean().optional(), timestamptz: z.boolean().optional(), + bool: z.boolean().optional(), relatedId: z.boolean().optional(), related: z.union([z.boolean(),z.lazy(() => DummyArgsSchema)]).optional(), }).strict() @@ -427,6 +429,7 @@ export const DataTypesWhereInputSchema: z.ZodType = timetz: z.union([ z.lazy(() => DateTimeNullableFilterSchema),z.coerce.date() ]).optional().nullable(), timestamp: z.union([ z.lazy(() => DateTimeNullableFilterSchema),z.coerce.date() ]).optional().nullable(), timestamptz: z.union([ z.lazy(() => DateTimeNullableFilterSchema),z.coerce.date() ]).optional().nullable(), + bool: z.union([ z.lazy(() => BoolNullableFilterSchema),z.boolean() ]).optional().nullable(), relatedId: z.union([ z.lazy(() => IntNullableFilterSchema),z.number() ]).optional().nullable(), related: z.union([ z.lazy(() => DummyRelationFilterSchema),z.lazy(() => DummyWhereInputSchema) ]).optional().nullable(), }).strict(); @@ -438,6 +441,7 @@ export const DataTypesOrderByWithRelationInputSchema: z.ZodType SortOrderSchema).optional(), timestamp: z.lazy(() => SortOrderSchema).optional(), timestamptz: z.lazy(() => SortOrderSchema).optional(), + bool: z.lazy(() => SortOrderSchema).optional(), relatedId: z.lazy(() => SortOrderSchema).optional(), related: z.lazy(() => DummyOrderByWithRelationInputSchema).optional() }).strict(); @@ -454,6 +458,7 @@ export const DataTypesOrderByWithAggregationInputSchema: z.ZodType SortOrderSchema).optional(), timestamp: z.lazy(() => SortOrderSchema).optional(), timestamptz: z.lazy(() => SortOrderSchema).optional(), + bool: z.lazy(() => SortOrderSchema).optional(), relatedId: z.lazy(() => SortOrderSchema).optional(), _count: z.lazy(() => DataTypesCountOrderByAggregateInputSchema).optional(), _avg: z.lazy(() => DataTypesAvgOrderByAggregateInputSchema).optional(), @@ -472,6 +477,7 @@ export const DataTypesScalarWhereWithAggregatesInputSchema: z.ZodType DateTimeNullableWithAggregatesFilterSchema),z.coerce.date() ]).optional().nullable(), timestamp: z.union([ z.lazy(() => DateTimeNullableWithAggregatesFilterSchema),z.coerce.date() ]).optional().nullable(), timestamptz: z.union([ z.lazy(() => DateTimeNullableWithAggregatesFilterSchema),z.coerce.date() ]).optional().nullable(), + bool: z.union([ z.lazy(() => BoolNullableWithAggregatesFilterSchema),z.boolean() ]).optional().nullable(), relatedId: z.union([ z.lazy(() => IntNullableWithAggregatesFilterSchema),z.number() ]).optional().nullable(), }).strict(); @@ -693,6 +699,7 @@ export const DataTypesCreateInputSchema: z.ZodType timetz: z.coerce.date().optional().nullable(), timestamp: z.coerce.date().optional().nullable(), timestamptz: z.coerce.date().optional().nullable(), + bool: z.boolean().optional().nullable(), related: z.lazy(() => DummyCreateNestedOneWithoutDatatypeInputSchema).optional() }).strict(); @@ -703,6 +710,7 @@ export const DataTypesUncheckedCreateInputSchema: z.ZodType timetz: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamp: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamptz: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + bool: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), related: z.lazy(() => DummyUpdateOneWithoutDatatypeNestedInputSchema).optional() }).strict(); @@ -723,6 +732,7 @@ export const DataTypesUncheckedUpdateInputSchema: z.ZodType NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamp: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamptz: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + bool: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), relatedId: z.union([ z.number().int(),z.lazy(() => NullableIntFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); @@ -733,6 +743,7 @@ export const DataTypesCreateManyInputSchema: z.ZodType NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamp: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamptz: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + bool: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); export const DataTypesUncheckedUpdateManyInputSchema: z.ZodType = z.object({ @@ -752,6 +764,7 @@ export const DataTypesUncheckedUpdateManyInputSchema: z.ZodType NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamp: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamptz: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + bool: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), relatedId: z.union([ z.number().int(),z.lazy(() => NullableIntFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); @@ -1055,6 +1068,11 @@ export const DateTimeNullableFilterSchema: z.ZodType NestedDateTimeNullableFilterSchema) ]).optional().nullable(), }).strict(); +export const BoolNullableFilterSchema: z.ZodType = z.object({ + equals: z.boolean().optional().nullable(), + not: z.union([ z.boolean(),z.lazy(() => NestedBoolNullableFilterSchema) ]).optional().nullable(), +}).strict(); + export const DummyRelationFilterSchema: z.ZodType = z.object({ is: z.lazy(() => DummyWhereInputSchema).optional().nullable(), isNot: z.lazy(() => DummyWhereInputSchema).optional().nullable() @@ -1067,6 +1085,7 @@ export const DataTypesCountOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), timestamp: z.lazy(() => SortOrderSchema).optional(), timestamptz: z.lazy(() => SortOrderSchema).optional(), + bool: z.lazy(() => SortOrderSchema).optional(), relatedId: z.lazy(() => SortOrderSchema).optional() }).strict(); @@ -1082,6 +1101,7 @@ export const DataTypesMaxOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), timestamp: z.lazy(() => SortOrderSchema).optional(), timestamptz: z.lazy(() => SortOrderSchema).optional(), + bool: z.lazy(() => SortOrderSchema).optional(), relatedId: z.lazy(() => SortOrderSchema).optional() }).strict(); @@ -1092,6 +1112,7 @@ export const DataTypesMinOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), timestamp: z.lazy(() => SortOrderSchema).optional(), timestamptz: z.lazy(() => SortOrderSchema).optional(), + bool: z.lazy(() => SortOrderSchema).optional(), relatedId: z.lazy(() => SortOrderSchema).optional() }).strict(); @@ -1114,6 +1135,14 @@ export const DateTimeNullableWithAggregatesFilterSchema: z.ZodType NestedDateTimeNullableFilterSchema).optional() }).strict(); +export const BoolNullableWithAggregatesFilterSchema: z.ZodType = z.object({ + equals: z.boolean().optional().nullable(), + not: z.union([ z.boolean(),z.lazy(() => NestedBoolNullableWithAggregatesFilterSchema) ]).optional().nullable(), + _count: z.lazy(() => NestedIntNullableFilterSchema).optional(), + _min: z.lazy(() => NestedBoolNullableFilterSchema).optional(), + _max: z.lazy(() => NestedBoolNullableFilterSchema).optional() +}).strict(); + export const DataTypesListRelationFilterSchema: z.ZodType = z.object({ every: z.lazy(() => DataTypesWhereInputSchema).optional(), some: z.lazy(() => DataTypesWhereInputSchema).optional(), @@ -1287,6 +1316,10 @@ export const NullableDateTimeFieldUpdateOperationsInputSchema: z.ZodType = z.object({ + set: z.boolean().optional().nullable() +}).strict(); + export const DummyUpdateOneWithoutDatatypeNestedInputSchema: z.ZodType = z.object({ create: z.union([ z.lazy(() => DummyCreateWithoutDatatypeInputSchema),z.lazy(() => DummyUncheckedCreateWithoutDatatypeInputSchema) ]).optional(), connectOrCreate: z.lazy(() => DummyCreateOrConnectWithoutDatatypeInputSchema).optional(), @@ -1488,6 +1521,11 @@ export const NestedDateTimeNullableFilterSchema: z.ZodType NestedDateTimeNullableFilterSchema) ]).optional().nullable(), }).strict(); +export const NestedBoolNullableFilterSchema: z.ZodType = z.object({ + equals: z.boolean().optional().nullable(), + not: z.union([ z.boolean(),z.lazy(() => NestedBoolNullableFilterSchema) ]).optional().nullable(), +}).strict(); + export const NestedDateTimeNullableWithAggregatesFilterSchema: z.ZodType = z.object({ equals: z.coerce.date().optional().nullable(), in: z.union([ z.coerce.date().array(),z.coerce.date() ]).optional().nullable(), @@ -1502,6 +1540,14 @@ export const NestedDateTimeNullableWithAggregatesFilterSchema: z.ZodType NestedDateTimeNullableFilterSchema).optional() }).strict(); +export const NestedBoolNullableWithAggregatesFilterSchema: z.ZodType = z.object({ + equals: z.boolean().optional().nullable(), + not: z.union([ z.boolean(),z.lazy(() => NestedBoolNullableWithAggregatesFilterSchema) ]).optional().nullable(), + _count: z.lazy(() => NestedIntNullableFilterSchema).optional(), + _min: z.lazy(() => NestedBoolNullableFilterSchema).optional(), + _max: z.lazy(() => NestedBoolNullableFilterSchema).optional() +}).strict(); + export const PostCreateWithoutAuthorInputSchema: z.ZodType = z.object({ id: z.number(), title: z.string(), @@ -1687,7 +1733,8 @@ export const DataTypesCreateWithoutRelatedInputSchema: z.ZodType = z.object({ @@ -1696,7 +1743,8 @@ export const DataTypesUncheckedCreateWithoutRelatedInputSchema: z.ZodType = z.object({ @@ -1735,6 +1783,7 @@ export const DataTypesScalarWhereInputSchema: z.ZodType DateTimeNullableFilterSchema),z.coerce.date() ]).optional().nullable(), timestamp: z.union([ z.lazy(() => DateTimeNullableFilterSchema),z.coerce.date() ]).optional().nullable(), timestamptz: z.union([ z.lazy(() => DateTimeNullableFilterSchema),z.coerce.date() ]).optional().nullable(), + bool: z.union([ z.lazy(() => BoolNullableFilterSchema),z.boolean() ]).optional().nullable(), relatedId: z.union([ z.lazy(() => IntNullableFilterSchema),z.number() ]).optional().nullable(), }).strict(); @@ -1772,7 +1821,8 @@ export const DataTypesCreateManyRelatedInputSchema: z.ZodType = z.object({ @@ -1782,6 +1832,7 @@ export const DataTypesUpdateWithoutRelatedInputSchema: z.ZodType NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamp: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamptz: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + bool: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); export const DataTypesUncheckedUpdateWithoutRelatedInputSchema: z.ZodType = z.object({ @@ -1791,6 +1842,7 @@ export const DataTypesUncheckedUpdateWithoutRelatedInputSchema: z.ZodType NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamp: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamptz: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + bool: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); export const DataTypesUncheckedUpdateManyWithoutDatatypeInputSchema: z.ZodType = z.object({ @@ -1800,6 +1852,7 @@ export const DataTypesUncheckedUpdateManyWithoutDatatypeInputSchema: z.ZodType

NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamp: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), timestamptz: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + bool: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); ///////////////////////////////////////// @@ -2465,7 +2518,16 @@ interface DummyGetPayload extends HKT { export const tableSchemas = { Items: { - fields: new Map([['value', 'TEXT'], ['nbr', 'INT4']]), + fields: new Map([ + [ + "value", + "TEXT" + ], + [ + "nbr", + "INT4" + ] + ]), relations: [ ], modelSchema: (ItemsCreateInputSchema as any) @@ -2493,7 +2555,16 @@ export const tableSchemas = { ItemsGetPayload >, User: { - fields: new Map([['id', 'TEXT'], ['name', 'TEXT']]), + fields: new Map([ + [ + "id", + "INT4" + ], + [ + "name", + "TEXT" + ] + ]), relations: [ new Relation("posts", "", "", "Post", "PostToUser", "many"), new Relation("profile", "", "", "Profile", "ProfileToUser", "one"), @@ -2524,11 +2595,26 @@ export const tableSchemas = { >, Post: { fields: new Map([ - ['id', 'TEXT'], - ['title', 'TEXT'], - ['contents', 'TEXT'], - ['nbr', 'INT4'], - ['authorId', 'TEXT'] + [ + "id", + "INT4" + ], + [ + "title", + "TEXT" + ], + [ + "contents", + "TEXT" + ], + [ + "nbr", + "INT4" + ], + [ + "authorId", + "INT4" + ] ]), relations: [ new Relation("author", "authorId", "id", "User", "PostToUser", "one"), @@ -2559,9 +2645,18 @@ export const tableSchemas = { >, Profile: { fields: new Map([ - ['id', 'TEXT'], - ['bio', 'TEXT'], - ['userId', 'TEXT'] + [ + "id", + "INT4" + ], + [ + "bio", + "TEXT" + ], + [ + "userId", + "INT4" + ] ]), relations: [ new Relation("user", "userId", "id", "User", "ProfileToUser", "one"), @@ -2592,13 +2687,38 @@ export const tableSchemas = { >, DataTypes: { fields: new Map([ - ['id', 'TEXT'], - ['date', 'DATE'], - ['time', 'TIME'], - ['timetz', 'TIMETZ'], - ['timestamp', 'TIMESTAMP'], - ['timestamptz', 'TIMESTAMPTZ'], - ['relatedId', 'INT4'] + [ + "id", + "INT4" + ], + [ + "date", + "DATE" + ], + [ + "time", + "TIME" + ], + [ + "timetz", + "TIMETZ" + ], + [ + "timestamp", + "TIMESTAMP" + ], + [ + "timestamptz", + "TIMESTAMPTZ" + ], + [ + "bool", + "BOOL" + ], + [ + "relatedId", + "INT4" + ] ]), relations: [ new Relation("related", "relatedId", "id", "Dummy", "DataTypesToDummy", "one"), @@ -2628,13 +2748,22 @@ export const tableSchemas = { DataTypesGetPayload >, Dummy: { - fields: new Map([["id", "INT4"], ["timestamp", "TIMESTAMP"]]), + fields: new Map([ + [ + "id", + "INT4" + ], + [ + "timestamp", + "TIMESTAMP" + ] + ]), relations: [ new Relation("datatype", "", "", "DataTypes", "DataTypesToDummy", "many"), ], modelSchema: (DummyCreateInputSchema as any) - .partial() - .or((DummyUncheckedCreateInputSchema as any).partial()), + .partial() + .or((DummyUncheckedCreateInputSchema as any).partial()), createSchema: DummyCreateArgsSchema, createManySchema: DummyCreateManyArgsSchema, findUniqueSchema: DummyFindUniqueArgsSchema, @@ -2645,16 +2774,16 @@ export const tableSchemas = { deleteSchema: DummyDeleteArgsSchema, deleteManySchema: DummyDeleteManyArgsSchema } as TableSchema< - z.infer, - Prisma.DummyCreateArgs['data'], - Prisma.DummyUpdateArgs['data'], - Prisma.DummyFindFirstArgs['select'], - Prisma.DummyFindFirstArgs['where'], - Prisma.DummyFindUniqueArgs['where'], - Omit, - Prisma.DummyFindFirstArgs['orderBy'], - Prisma.DummyScalarFieldEnum, - DummyGetPayload + z.infer, + Prisma.DummyCreateArgs['data'], + Prisma.DummyUpdateArgs['data'], + Prisma.DummyFindFirstArgs['select'], + Prisma.DummyFindFirstArgs['where'], + Prisma.DummyFindUniqueArgs['where'], + Omit, + Prisma.DummyFindFirstArgs['orderBy'], + Prisma.DummyScalarFieldEnum, + DummyGetPayload >, } diff --git a/clients/typescript/test/client/model/datatype.test.ts b/clients/typescript/test/client/model/datatype.test.ts index b36175501d..c136ed8f86 100644 --- a/clients/typescript/test/client/model/datatype.test.ts +++ b/clients/typescript/test/client/model/datatype.test.ts @@ -9,6 +9,7 @@ import { _RECORD_NOT_FOUND_, } from '../../../src/client/validation/errors/messages' import { schema } from '../generated' +import { ZodError } from 'zod' const db = new Database(':memory:') const electric = await electrify( @@ -30,7 +31,7 @@ await tbl.sync() function setupDB() { db.exec('DROP TABLE IF EXISTS DataTypes') db.exec( - "CREATE TABLE DataTypes('id' int PRIMARY KEY, 'date' varchar, 'time' varchar, 'timetz' varchar, 'timestamp' varchar, 'timestamptz' varchar, 'relatedId' int);" + "CREATE TABLE DataTypes('id' int PRIMARY KEY, 'date' varchar, 'time' varchar, 'timetz' varchar, 'timestamp' varchar, 'timestamptz' varchar, 'bool' int, 'relatedId' int);" ) } @@ -256,3 +257,91 @@ test.serial('support null value for timestamptz type', async (t) => { t.deepEqual(fetchRes, expectedRes) }) + +test.serial('support boolean type', async (t) => { + // Check that we can store booleans + const res = await tbl.createMany({ + data: [ + { + id: 1, + bool: true, + }, + { + id: 2, + bool: false, + }, + ], + }) + + t.deepEqual(res, { + count: 2, + }) + + const rows = await tbl.findMany({ + select: { + id: true, + bool: true, + }, + orderBy: { + id: 'asc', + }, + }) + + t.deepEqual(rows, [ + { + id: 1, + bool: true, + }, + { + id: 2, + bool: false, + }, + ]) + + // Check that it rejects invalid values + await t.throwsAsync( + tbl.create({ + data: { + id: 3, + // @ts-ignore + bool: 'true', + }, + }), + { + instanceOf: ZodError, + message: /Expected boolean, received string/, + } + ) +}) + +test.serial('support null value for boolean type', async (t) => { + const expectedRes = { + id: 1, + bool: null, + } + + const res = await tbl.create({ + data: { + id: 1, + bool: null, + }, + select: { + id: true, + bool: true, + }, + }) + + t.deepEqual(res, expectedRes) + + const fetchRes = await tbl.findUnique({ + where: { + id: 1, + }, + select: { + id: true, + bool: true, + }, + }) + + t.deepEqual(fetchRes, expectedRes) +}) diff --git a/clients/typescript/test/client/prisma/schema.prisma b/clients/typescript/test/client/prisma/schema.prisma index eadab9739d..46b439174a 100644 --- a/clients/typescript/test/client/prisma/schema.prisma +++ b/clients/typescript/test/client/prisma/schema.prisma @@ -50,6 +50,7 @@ model DataTypes { timetz DateTime? @db.Timetz(3) timestamp DateTime? @unique @db.Timestamp(3) timestamptz DateTime? @db.Timestamptz(3) + bool Boolean? relatedId Int? related Dummy? @relation(fields: [relatedId], references: [id]) } diff --git a/clients/typescript/test/satellite/client.test.ts b/clients/typescript/test/satellite/client.test.ts index 32b8e7bd2a..2f15a951e6 100644 --- a/clients/typescript/test/satellite/client.test.ts +++ b/clients/typescript/test/satellite/client.test.ts @@ -19,8 +19,11 @@ import { SatelliteErrorCode, Transaction, } from '../../src/util/types' -import { relations } from './common' +import { dbDescription, relations } from './common' import { RpcResponse, SatelliteWSServerStub } from './server_ws_stub' +import { DbSchema, TableSchema } from '../../src/client/model/schema' +import { PgBasicType } from '../../src/client/conversions/types' +import { HKT } from '../../src/client/util/hkt' interface Context extends AuthState { server: SatelliteWSServerStub @@ -38,6 +41,7 @@ test.beforeEach((t) => { const client = new SatelliteClient( dbName, + dbDescription, WebSocketNode, new MockNotifier(dbName), { @@ -220,6 +224,32 @@ test.serial('receive transaction over multiple messages', async (t) => { await connectAndAuth(t.context) const { client, server } = t.context + const dbDescription = new DbSchema( + { + table: { + fields: new Map([ + ['name1', PgBasicType.PG_TEXT], + ['name2', PgBasicType.PG_TEXT], + ]), + relations: [], + } as unknown as TableSchema< + any, + any, + any, + any, + any, + any, + any, + any, + any, + HKT + >, + }, + [] + ) + + client['dbDescription'] = dbDescription + const start = Proto.SatInStartReplicationResp.create() const begin = Proto.SatOpBegin.fromPartial({ commitTimestamp: Long.ZERO }) const commit = Proto.SatOpCommit.create() @@ -256,17 +286,25 @@ test.serial('receive transaction over multiple messages', async (t) => { const insertOp = Proto.SatOpInsert.fromPartial({ relationId: 1, - rowData: serializeRow({ name1: 'Foo', name2: 'Bar' }, rel), + rowData: serializeRow({ name1: 'Foo', name2: 'Bar' }, rel, dbDescription), }) const updateOp = Proto.SatOpUpdate.fromPartial({ relationId: 1, - rowData: serializeRow({ name1: 'Hello', name2: 'World!' }, rel), - oldRowData: serializeRow({ name1: '', name2: '' }, rel), + rowData: serializeRow( + { name1: 'Hello', name2: 'World!' }, + rel, + dbDescription + ), + oldRowData: serializeRow({ name1: '', name2: '' }, rel, dbDescription), }) const deleteOp = Proto.SatOpDelete.fromPartial({ relationId: 1, - oldRowData: serializeRow({ name1: 'Hello', name2: 'World!' }, rel), + oldRowData: serializeRow( + { name1: 'Hello', name2: 'World!' }, + rel, + dbDescription + ), }) const firstOpLogMessage = Proto.SatOpLog.fromPartial({ @@ -598,6 +636,28 @@ test.serial('default and null test', async (t) => { ], }) + const tbl = { + fields: new Map([ + ['id', PgBasicType.PG_UUID], + ['content', PgBasicType.PG_VARCHAR], + ['text_null', PgBasicType.PG_TEXT], + ['text_null_default', PgBasicType.PG_TEXT], + ['intvalue_null', PgBasicType.PG_INT4], + ['intvalue_null_default', PgBasicType.PG_INT4], + ]), + relations: [], + } as unknown as TableSchema + + const dbDescription = new DbSchema( + { + table: tbl, + Items: tbl, + }, + [] + ) + + client['dbDescription'] = dbDescription + const insertOp = Proto.SatOpInsert.fromPartial({ relationId: 1, rowData: serializeRow( @@ -609,7 +669,8 @@ test.serial('default and null test', async (t) => { intvalue_null: null, intvalue_null_default: '10', }, - rel + rel, + dbDescription ), }) @@ -632,7 +693,7 @@ test.serial('default and null test', async (t) => { ], } - const record: any = deserializeRow(serializedRow, rel)! + const record: any = deserializeRow(serializedRow, rel, dbDescription)! const firstOpLogMessage = Proto.SatOpLog.fromPartial({ ops: [ @@ -885,6 +946,28 @@ test.serial('subscription incorrect protocol sequence', async (t) => { test.serial('subscription correct protocol sequence with data', async (t) => { await connectAndAuth(t.context) const { client, server } = t.context + + const tablename = 'THE_TABLE_ID' + + const tbl = { + fields: new Map([ + ['name1', PgBasicType.PG_TEXT], + ['name2', PgBasicType.PG_TEXT], + ]), + relations: [], + } as unknown as TableSchema + + const dbDescription = new DbSchema( + { + table: tbl, + [tablename]: tbl, + }, + [] + ) + + client['dbDescription'] = dbDescription + client['subscriptionsDataCache']['dbDescription'] = dbDescription + await startReplication(client, server) const rel: Relation = { @@ -906,7 +989,6 @@ test.serial('subscription correct protocol sequence with data', async (t) => { const subscriptionId = 'THE_SUBS_ID' const uuid1 = 'THE_SHAPE_ID_1' const uuid2 = 'THE_SHAPE_ID_2' - const tablename = 'THE_TABLE_ID' const shapeReq1: ShapeRequest = { requestId: requestId1, @@ -949,7 +1031,7 @@ test.serial('subscription correct protocol sequence with data', async (t) => { const insertOp = Proto.SatOpInsert.fromPartial({ relationId: 0, - rowData: serializeRow({ name1: 'Foo', name2: 'Bar' }, rel), + rowData: serializeRow({ name1: 'Foo', name2: 'Bar' }, rel, dbDescription), }) const satTransOpInsert = Proto.SatTransOp.fromPartial({ insert: insertOp }) diff --git a/clients/typescript/test/satellite/common.ts b/clients/typescript/test/satellite/common.ts index 600a0bf1af..db36325856 100644 --- a/clients/typescript/test/satellite/common.ts +++ b/clients/typescript/test/satellite/common.ts @@ -16,6 +16,34 @@ import { Satellite, SatelliteProcess } from '../../src/satellite' import { TableInfo, initTableInfo } from '../support/satellite-helpers' import { satelliteDefaults, SatelliteOpts } from '../../src/satellite/config' +export const dbDescription = new DbSchema( + { + child: { + fields: new Map([ + ['id', PgBasicType.PG_INTEGER], + ['parent', PgBasicType.PG_INTEGER], + ]), + relations: [], + }, + parent: { + fields: new Map([ + ['id', PgBasicType.PG_INTEGER], + ['value', PgBasicType.PG_TEXT], + ['other', PgBasicType.PG_INTEGER], + ]), + relations: [], + }, + another: { + fields: new Map([['id', PgBasicType.PG_INTEGER]]), + relations: [], + }, + } as unknown as Record< + string, + TableSchema + >, + [] +) + export const relations = { child: { id: 0, @@ -83,6 +111,9 @@ import migrations from '../support/migrations/migrations.js' import { ExecutionContext } from 'ava' import { AuthState } from '../../src/auth' import { OplogEntry } from '../../src/satellite/oplog' +import { DbSchema, TableSchema } from '../../src/client/model/schema' +import { PgBasicType } from '../../src/client/conversions/types' +import { HKT } from '../../src/client/util/hkt' // Speed up the intervals for testing. export const opts = Object.assign({}, satelliteDefaults, { diff --git a/clients/typescript/test/satellite/registry.test.ts b/clients/typescript/test/satellite/registry.test.ts index 3b928f6267..8ee253bb0f 100644 --- a/clients/typescript/test/satellite/registry.test.ts +++ b/clients/typescript/test/satellite/registry.test.ts @@ -6,9 +6,11 @@ import { Migrator } from '../../src/migrators/index' import { Notifier } from '../../src/notifiers/index' import { MockSatelliteProcess, MockRegistry } from '../../src/satellite/mock' import { SocketFactory } from '../../src/sockets' +import { DbSchema } from '../../src/client/model' const dbName = 'test.db' +const dbDescription = {} as DbSchema const adapter = {} as DatabaseAdapter const migrator = {} as Migrator const notifier = {} as Notifier @@ -20,6 +22,7 @@ const config: ElectricConfig = { } const args = [ dbName, + dbDescription, adapter, migrator, notifier, @@ -38,6 +41,7 @@ test('starting multiple satellite processes works', async (t) => { const mockRegistry = new MockRegistry() const s1 = await mockRegistry.startProcess( 'a.db', + dbDescription, adapter, migrator, notifier, @@ -46,6 +50,7 @@ test('starting multiple satellite processes works', async (t) => { ) const s2 = await mockRegistry.startProcess( 'b.db', + dbDescription, adapter, migrator, notifier, @@ -54,6 +59,7 @@ test('starting multiple satellite processes works', async (t) => { ) const s3 = await mockRegistry.startProcess( 'c.db', + dbDescription, adapter, migrator, notifier, @@ -77,6 +83,7 @@ test('ensure starting multiple satellite processes works', async (t) => { const mockRegistry = new MockRegistry() const s1 = await mockRegistry.ensureStarted( 'a.db', + dbDescription, adapter, migrator, notifier, @@ -85,6 +92,7 @@ test('ensure starting multiple satellite processes works', async (t) => { ) const s2 = await mockRegistry.ensureStarted( 'b.db', + dbDescription, adapter, migrator, notifier, @@ -93,6 +101,7 @@ test('ensure starting multiple satellite processes works', async (t) => { ) const s3 = await mockRegistry.ensureStarted( 'c.db', + dbDescription, adapter, migrator, notifier, @@ -173,6 +182,7 @@ test('stopAll works', async (t) => { const [_s1, _s2, _s3] = await Promise.all([ mockRegistry.ensureStarted( 'a.db', + dbDescription, adapter, migrator, notifier, @@ -181,6 +191,7 @@ test('stopAll works', async (t) => { ), mockRegistry.ensureStarted( 'b.db', + dbDescription, adapter, migrator, notifier, @@ -189,6 +200,7 @@ test('stopAll works', async (t) => { ), mockRegistry.ensureStarted( 'c.db', + dbDescription, adapter, migrator, notifier, @@ -206,6 +218,7 @@ test('stopAll works even when starting', async (t) => { const startPromises = [ mockRegistry.ensureStarted( 'a.db', + dbDescription, adapter, migrator, notifier, @@ -214,6 +227,7 @@ test('stopAll works even when starting', async (t) => { ), mockRegistry.ensureStarted( 'b.db', + dbDescription, adapter, migrator, notifier, @@ -222,6 +236,7 @@ test('stopAll works even when starting', async (t) => { ), mockRegistry.ensureStarted( 'c.db', + dbDescription, adapter, migrator, notifier, @@ -241,6 +256,7 @@ test('stopAll works across running, stopping and starting', async (t) => { const mockRegistry = new MockRegistry() await mockRegistry.ensureStarted( 'a.db', + dbDescription, adapter, migrator, notifier, @@ -249,6 +265,7 @@ test('stopAll works across running, stopping and starting', async (t) => { ) await mockRegistry.ensureStarted( 'b.db', + dbDescription, adapter, migrator, notifier, @@ -257,6 +274,7 @@ test('stopAll works across running, stopping and starting', async (t) => { ) const p1 = mockRegistry.ensureStarted( 'c.db', + dbDescription, adapter, migrator, notifier, @@ -265,6 +283,7 @@ test('stopAll works across running, stopping and starting', async (t) => { ) const p2 = mockRegistry.ensureStarted( 'd.db', + dbDescription, adapter, migrator, notifier, diff --git a/clients/typescript/test/satellite/serialization.test.ts b/clients/typescript/test/satellite/serialization.test.ts index a13a2d871f..9f2951aca7 100644 --- a/clients/typescript/test/satellite/serialization.test.ts +++ b/clients/typescript/test/satellite/serialization.test.ts @@ -2,6 +2,13 @@ import { SatRelation_RelationType } from '../../src/_generated/protocol/satellit import { serializeRow, deserializeRow } from '../../src/satellite/client' import test from 'ava' import { Relation, Record } from '../../src/util/types' +import { DbSchema, TableSchema } from '../../src/client/model/schema' +import { PgBasicType } from '../../src/client/conversions/types' +import { HKT } from '../../src/client/util/hkt' +import Database from 'better-sqlite3' +import { DatabaseAdapter } from '../../src/drivers/better-sqlite3' +import { inferRelationsFromSQLite } from '../../src/util/relations' +import { satelliteDefaults } from '../../src/satellite/config' test('serialize/deserialize row data', async (t) => { const rel: Relation = { @@ -23,6 +30,38 @@ test('serialize/deserialize row data', async (t) => { ], } + const dbDescription = new DbSchema( + { + table: { + fields: new Map([ + ['name1', PgBasicType.PG_TEXT], + ['name2', PgBasicType.PG_TEXT], + ['name3', PgBasicType.PG_TEXT], + ['int1', PgBasicType.PG_INTEGER], + ['int2', PgBasicType.PG_INTEGER], + ['float1', PgBasicType.PG_REAL], + ['float2', PgBasicType.PG_FLOAT4], + ['bool1', PgBasicType.PG_BOOL], + ['bool2', PgBasicType.PG_BOOL], + ['bool3', PgBasicType.PG_BOOL], + ]), + relations: [], + } as unknown as TableSchema< + any, + any, + any, + any, + any, + any, + any, + any, + any, + HKT + >, + }, + [] + ) + const record: Record = { name1: 'Hello', name2: 'World!', @@ -36,13 +75,13 @@ test('serialize/deserialize row data', async (t) => { bool3: null, } - const s_row = serializeRow(record, rel) + const s_row = serializeRow(record, rel, dbDescription) t.deepEqual( s_row.values.map((bytes) => new TextDecoder().decode(bytes)), ['Hello', 'World!', '', '1', '-30', '1.0', '-30.3', 't', 'f', ''] ) - const d_row = deserializeRow(s_row, rel) + const d_row = deserializeRow(s_row, rel, dbDescription) t.deepEqual(record, d_row) }) @@ -65,6 +104,37 @@ test('Null mask uses bits as if they were a list', async (t) => { ], } + const dbDescription = new DbSchema( + { + table: { + fields: new Map([ + ['bit0', PgBasicType.PG_TEXT], + ['bit1', PgBasicType.PG_TEXT], + ['bit2', PgBasicType.PG_TEXT], + ['bit3', PgBasicType.PG_TEXT], + ['bit4', PgBasicType.PG_TEXT], + ['bit5', PgBasicType.PG_TEXT], + ['bit6', PgBasicType.PG_TEXT], + ['bit7', PgBasicType.PG_TEXT], + ['bit8', PgBasicType.PG_TEXT], + ]), + relations: [], + } as unknown as TableSchema< + any, + any, + any, + any, + any, + any, + any, + any, + any, + HKT + >, + }, + [] + ) + const record: Record = { bit0: null, bit1: null, @@ -76,9 +146,117 @@ test('Null mask uses bits as if they were a list', async (t) => { bit7: 'Filled', bit8: null, } - const s_row = serializeRow(record, rel) + const s_row = serializeRow(record, rel, dbDescription) const mask = [...s_row.nullsBitmask].map((x) => x.toString(2)).join('') t.is(mask, '1101000010000000') }) + +test('Prioritize PG types in the schema before inferred SQLite types', async (t) => { + const db = new Database(':memory:') + t.teardown(() => db.close()) + + const adapter = new DatabaseAdapter(db) + await adapter.run({ + sql: 'CREATE TABLE bools (id INTEGER PRIMARY KEY, b INTEGER)', + }) + + const sqliteInferredRelations = await inferRelationsFromSQLite( + adapter, + satelliteDefaults + ) + const boolsInferredRelation = sqliteInferredRelations['bools'] + + // Inferred types only support SQLite types, so the bool column is INTEGER + const boolColumn = boolsInferredRelation.columns[1] + t.is(boolColumn.name, 'b') + t.is(boolColumn.type, 'INTEGER') + + // Db schema holds the correct Postgres types + const boolsDbDescription = new DbSchema( + { + bools: { + fields: new Map([ + ['id', PgBasicType.PG_INTEGER], + ['b', PgBasicType.PG_BOOL], + ]), + relations: [], + } as unknown as TableSchema< + any, + any, + any, + any, + any, + any, + any, + any, + any, + HKT + >, + }, + [] + ) + + const satOpRow = serializeRow( + { id: 5, b: 1 }, + boolsInferredRelation, + boolsDbDescription + ) + + // Encoded values ["5", "t"] + t.deepEqual(satOpRow.values, [ + new Uint8Array(['5'.charCodeAt(0)]), + new Uint8Array(['t'.charCodeAt(0)]), + ]) + + const deserializedRow = deserializeRow( + satOpRow, + boolsInferredRelation, + boolsDbDescription + ) + + t.deepEqual(deserializedRow, { id: 5, b: 1 }) +}) + +test('Use incoming Relation types if not found in the schema', async (t) => { + const db = new Database(':memory:') + t.teardown(() => db.close()) + + const adapter = new DatabaseAdapter(db) + + const sqliteInferredRelations = await inferRelationsFromSQLite( + adapter, + satelliteDefaults + ) + // Empty database + t.is(Object.keys(sqliteInferredRelations).length, 0) + + // Empty Db schema + const testDbDescription = new DbSchema({}, []) + + const newTableRelation: Relation = { + id: 1, + schema: 'schema', + table: 'new_table', + tableType: SatRelation_RelationType.TABLE, + columns: [{ name: 'value', type: 'INTEGER', isNullable: true }], + } + + const satOpRow = serializeRow( + { value: 6 }, + newTableRelation, + testDbDescription + ) + + // Encoded values ["6"] + t.deepEqual(satOpRow.values, [new Uint8Array(['6'.charCodeAt(0)])]) + + const deserializedRow = deserializeRow( + satOpRow, + newTableRelation, + testDbDescription + ) + + t.deepEqual(deserializedRow, { value: 6 }) +}) diff --git a/e2e/satellite_client/src/client.ts b/e2e/satellite_client/src/client.ts index 6696a02f10..9108066c7d 100644 --- a/e2e/satellite_client/src/client.ts +++ b/e2e/satellite_client/src/client.ts @@ -136,6 +136,24 @@ export const check_datetime = (datetime: Datetime | undefined, expectedDate: str datetime!.t.getTime() === new Date(expectedTime).getTime() } +export const write_bool = (electric: Electric, id: string, b: boolean) => { + return electric.db.bools.create({ + data: { + id, + b + } + }) +} + +export const get_bool = async (electric: Electric, id: string): Promise => { + const row = await electric.db.bools.findUnique({ + where: { + id: id + }, + }) + return row.b +} + export const get_datetimes = (electric: Electric) => { return electric.db.datetimes.findMany() } diff --git a/e2e/satellite_client/src/generated/client/index.ts b/e2e/satellite_client/src/generated/client/index.ts index efa3d1d9eb..1d2ecaf1f3 100644 --- a/e2e/satellite_client/src/generated/client/index.ts +++ b/e2e/satellite_client/src/generated/client/index.ts @@ -12,6 +12,8 @@ import migrations from './migrations'; // ENUMS ///////////////////////////////////////// +export const BoolsScalarFieldEnumSchema = z.enum(['id','b']); + export const DatetimesScalarFieldEnumSchema = z.enum(['id','d','t']); export const ItemsScalarFieldEnumSchema = z.enum(['id','content','content_text_null','content_text_null_default','intvalue_null','intvalue_null_default']); @@ -80,6 +82,17 @@ export const DatetimesSchema = z.object({ export type Datetimes = z.infer +///////////////////////////////////////// +// BOOLS SCHEMA +///////////////////////////////////////// + +export const BoolsSchema = z.object({ + id: z.string(), + b: z.boolean().nullish(), +}) + +export type Bools = z.infer + ///////////////////////////////////////// // SELECT & INCLUDE ///////////////////////////////////////// @@ -143,6 +156,14 @@ export const DatetimesSelectSchema: z.ZodType = z.object t: z.boolean().optional(), }).strict() +// BOOLS +//------------------------------------------------------ + +export const BoolsSelectSchema: z.ZodType = z.object({ + id: z.boolean().optional(), + b: z.boolean().optional(), +}).strict() + ///////////////////////////////////////// // INPUT TYPES @@ -315,6 +336,39 @@ export const DatetimesScalarWhereWithAggregatesInputSchema: z.ZodType DateTimeWithAggregatesFilterSchema),z.coerce.date() ]).optional(), }).strict(); +export const BoolsWhereInputSchema: z.ZodType = z.object({ + AND: z.union([ z.lazy(() => BoolsWhereInputSchema),z.lazy(() => BoolsWhereInputSchema).array() ]).optional(), + OR: z.lazy(() => BoolsWhereInputSchema).array().optional(), + NOT: z.union([ z.lazy(() => BoolsWhereInputSchema),z.lazy(() => BoolsWhereInputSchema).array() ]).optional(), + id: z.union([ z.lazy(() => StringFilterSchema),z.string() ]).optional(), + b: z.union([ z.lazy(() => BoolNullableFilterSchema),z.boolean() ]).optional().nullable(), +}).strict(); + +export const BoolsOrderByWithRelationInputSchema: z.ZodType = z.object({ + id: z.lazy(() => SortOrderSchema).optional(), + b: z.lazy(() => SortOrderSchema).optional() +}).strict(); + +export const BoolsWhereUniqueInputSchema: z.ZodType = z.object({ + id: z.string().optional() +}).strict(); + +export const BoolsOrderByWithAggregationInputSchema: z.ZodType = z.object({ + id: z.lazy(() => SortOrderSchema).optional(), + b: z.lazy(() => SortOrderSchema).optional(), + _count: z.lazy(() => BoolsCountOrderByAggregateInputSchema).optional(), + _max: z.lazy(() => BoolsMaxOrderByAggregateInputSchema).optional(), + _min: z.lazy(() => BoolsMinOrderByAggregateInputSchema).optional() +}).strict(); + +export const BoolsScalarWhereWithAggregatesInputSchema: z.ZodType = z.object({ + AND: z.union([ z.lazy(() => BoolsScalarWhereWithAggregatesInputSchema),z.lazy(() => BoolsScalarWhereWithAggregatesInputSchema).array() ]).optional(), + OR: z.lazy(() => BoolsScalarWhereWithAggregatesInputSchema).array().optional(), + NOT: z.union([ z.lazy(() => BoolsScalarWhereWithAggregatesInputSchema),z.lazy(() => BoolsScalarWhereWithAggregatesInputSchema).array() ]).optional(), + id: z.union([ z.lazy(() => StringWithAggregatesFilterSchema),z.string() ]).optional(), + b: z.union([ z.lazy(() => BoolNullableWithAggregatesFilterSchema),z.boolean() ]).optional().nullable(), +}).strict(); + export const ItemsCreateInputSchema: z.ZodType = z.object({ id: z.string(), content: z.string(), @@ -507,6 +561,41 @@ export const DatetimesUncheckedUpdateManyInputSchema: z.ZodType DateTimeFieldUpdateOperationsInputSchema) ]).optional(), }).strict(); +export const BoolsCreateInputSchema: z.ZodType = z.object({ + id: z.string(), + b: z.boolean().optional().nullable() +}).strict(); + +export const BoolsUncheckedCreateInputSchema: z.ZodType = z.object({ + id: z.string(), + b: z.boolean().optional().nullable() +}).strict(); + +export const BoolsUpdateInputSchema: z.ZodType = z.object({ + id: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), + b: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), +}).strict(); + +export const BoolsUncheckedUpdateInputSchema: z.ZodType = z.object({ + id: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), + b: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), +}).strict(); + +export const BoolsCreateManyInputSchema: z.ZodType = z.object({ + id: z.string(), + b: z.boolean().optional().nullable() +}).strict(); + +export const BoolsUpdateManyMutationInputSchema: z.ZodType = z.object({ + id: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), + b: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), +}).strict(); + +export const BoolsUncheckedUpdateManyInputSchema: z.ZodType = z.object({ + id: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), + b: z.union([ z.boolean(),z.lazy(() => NullableBoolFieldUpdateOperationsInputSchema) ]).optional().nullable(), +}).strict(); + export const StringFilterSchema: z.ZodType = z.object({ equals: z.string().optional(), in: z.union([ z.string().array(),z.string() ]).optional(), @@ -726,6 +815,34 @@ export const DatetimesMinOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional() }).strict(); +export const BoolNullableFilterSchema: z.ZodType = z.object({ + equals: z.boolean().optional().nullable(), + not: z.union([ z.boolean(),z.lazy(() => NestedBoolNullableFilterSchema) ]).optional().nullable(), +}).strict(); + +export const BoolsCountOrderByAggregateInputSchema: z.ZodType = z.object({ + id: z.lazy(() => SortOrderSchema).optional(), + b: z.lazy(() => SortOrderSchema).optional() +}).strict(); + +export const BoolsMaxOrderByAggregateInputSchema: z.ZodType = z.object({ + id: z.lazy(() => SortOrderSchema).optional(), + b: z.lazy(() => SortOrderSchema).optional() +}).strict(); + +export const BoolsMinOrderByAggregateInputSchema: z.ZodType = z.object({ + id: z.lazy(() => SortOrderSchema).optional(), + b: z.lazy(() => SortOrderSchema).optional() +}).strict(); + +export const BoolNullableWithAggregatesFilterSchema: z.ZodType = z.object({ + equals: z.boolean().optional().nullable(), + not: z.union([ z.boolean(),z.lazy(() => NestedBoolNullableWithAggregatesFilterSchema) ]).optional().nullable(), + _count: z.lazy(() => NestedIntNullableFilterSchema).optional(), + _min: z.lazy(() => NestedBoolNullableFilterSchema).optional(), + _max: z.lazy(() => NestedBoolNullableFilterSchema).optional() +}).strict(); + export const OtherItemsCreateNestedOneWithoutItemsInputSchema: z.ZodType = z.object({ create: z.union([ z.lazy(() => OtherItemsCreateWithoutItemsInputSchema),z.lazy(() => OtherItemsUncheckedCreateWithoutItemsInputSchema) ]).optional(), connectOrCreate: z.lazy(() => OtherItemsCreateOrConnectWithoutItemsInputSchema).optional(), @@ -794,6 +911,10 @@ export const DateTimeFieldUpdateOperationsInputSchema: z.ZodType = z.object({ + set: z.boolean().optional().nullable() +}).strict(); + export const NestedStringFilterSchema: z.ZodType = z.object({ equals: z.string().optional(), in: z.union([ z.string().array(),z.string() ]).optional(), @@ -930,6 +1051,19 @@ export const NestedDateTimeWithAggregatesFilterSchema: z.ZodType NestedDateTimeFilterSchema).optional() }).strict(); +export const NestedBoolNullableFilterSchema: z.ZodType = z.object({ + equals: z.boolean().optional().nullable(), + not: z.union([ z.boolean(),z.lazy(() => NestedBoolNullableFilterSchema) ]).optional().nullable(), +}).strict(); + +export const NestedBoolNullableWithAggregatesFilterSchema: z.ZodType = z.object({ + equals: z.boolean().optional().nullable(), + not: z.union([ z.boolean(),z.lazy(() => NestedBoolNullableWithAggregatesFilterSchema) ]).optional().nullable(), + _count: z.lazy(() => NestedIntNullableFilterSchema).optional(), + _min: z.lazy(() => NestedBoolNullableFilterSchema).optional(), + _max: z.lazy(() => NestedBoolNullableFilterSchema).optional() +}).strict(); + export const OtherItemsCreateWithoutItemsInputSchema: z.ZodType = z.object({ id: z.string(), content: z.string() @@ -1248,6 +1382,63 @@ export const DatetimesFindUniqueOrThrowArgsSchema: z.ZodType = z.object({ + select: BoolsSelectSchema.optional(), + where: BoolsWhereInputSchema.optional(), + orderBy: z.union([ BoolsOrderByWithRelationInputSchema.array(),BoolsOrderByWithRelationInputSchema ]).optional(), + cursor: BoolsWhereUniqueInputSchema.optional(), + take: z.number().optional(), + skip: z.number().optional(), + distinct: BoolsScalarFieldEnumSchema.array().optional(), +}).strict() + +export const BoolsFindFirstOrThrowArgsSchema: z.ZodType = z.object({ + select: BoolsSelectSchema.optional(), + where: BoolsWhereInputSchema.optional(), + orderBy: z.union([ BoolsOrderByWithRelationInputSchema.array(),BoolsOrderByWithRelationInputSchema ]).optional(), + cursor: BoolsWhereUniqueInputSchema.optional(), + take: z.number().optional(), + skip: z.number().optional(), + distinct: BoolsScalarFieldEnumSchema.array().optional(), +}).strict() + +export const BoolsFindManyArgsSchema: z.ZodType = z.object({ + select: BoolsSelectSchema.optional(), + where: BoolsWhereInputSchema.optional(), + orderBy: z.union([ BoolsOrderByWithRelationInputSchema.array(),BoolsOrderByWithRelationInputSchema ]).optional(), + cursor: BoolsWhereUniqueInputSchema.optional(), + take: z.number().optional(), + skip: z.number().optional(), + distinct: BoolsScalarFieldEnumSchema.array().optional(), +}).strict() + +export const BoolsAggregateArgsSchema: z.ZodType = z.object({ + where: BoolsWhereInputSchema.optional(), + orderBy: z.union([ BoolsOrderByWithRelationInputSchema.array(),BoolsOrderByWithRelationInputSchema ]).optional(), + cursor: BoolsWhereUniqueInputSchema.optional(), + take: z.number().optional(), + skip: z.number().optional(), +}).strict() + +export const BoolsGroupByArgsSchema: z.ZodType = z.object({ + where: BoolsWhereInputSchema.optional(), + orderBy: z.union([ BoolsOrderByWithAggregationInputSchema.array(),BoolsOrderByWithAggregationInputSchema ]).optional(), + by: BoolsScalarFieldEnumSchema.array(), + having: BoolsScalarWhereWithAggregatesInputSchema.optional(), + take: z.number().optional(), + skip: z.number().optional(), +}).strict() + +export const BoolsFindUniqueArgsSchema: z.ZodType = z.object({ + select: BoolsSelectSchema.optional(), + where: BoolsWhereUniqueInputSchema, +}).strict() + +export const BoolsFindUniqueOrThrowArgsSchema: z.ZodType = z.object({ + select: BoolsSelectSchema.optional(), + where: BoolsWhereUniqueInputSchema, +}).strict() + export const ItemsCreateArgsSchema: z.ZodType = z.object({ select: ItemsSelectSchema.optional(), include: ItemsIncludeSchema.optional(), @@ -1404,6 +1595,43 @@ export const DatetimesDeleteManyArgsSchema: z.ZodType = z.object({ + select: BoolsSelectSchema.optional(), + data: z.union([ BoolsCreateInputSchema,BoolsUncheckedCreateInputSchema ]), +}).strict() + +export const BoolsUpsertArgsSchema: z.ZodType = z.object({ + select: BoolsSelectSchema.optional(), + where: BoolsWhereUniqueInputSchema, + create: z.union([ BoolsCreateInputSchema,BoolsUncheckedCreateInputSchema ]), + update: z.union([ BoolsUpdateInputSchema,BoolsUncheckedUpdateInputSchema ]), +}).strict() + +export const BoolsCreateManyArgsSchema: z.ZodType = z.object({ + data: z.union([ BoolsCreateManyInputSchema,BoolsCreateManyInputSchema.array() ]), + skipDuplicates: z.boolean().optional(), +}).strict() + +export const BoolsDeleteArgsSchema: z.ZodType = z.object({ + select: BoolsSelectSchema.optional(), + where: BoolsWhereUniqueInputSchema, +}).strict() + +export const BoolsUpdateArgsSchema: z.ZodType = z.object({ + select: BoolsSelectSchema.optional(), + data: z.union([ BoolsUpdateInputSchema,BoolsUncheckedUpdateInputSchema ]), + where: BoolsWhereUniqueInputSchema, +}).strict() + +export const BoolsUpdateManyArgsSchema: z.ZodType = z.object({ + data: z.union([ BoolsUpdateManyMutationInputSchema,BoolsUncheckedUpdateManyInputSchema ]), + where: BoolsWhereInputSchema.optional(), +}).strict() + +export const BoolsDeleteManyArgsSchema: z.ZodType = z.object({ + where: BoolsWhereInputSchema.optional(), +}).strict() + interface ItemsGetPayload extends HKT { readonly _A?: boolean | null | undefined | Prisma.ItemsArgs readonly type: Prisma.ItemsGetPayload @@ -1424,6 +1652,11 @@ interface DatetimesGetPayload extends HKT { readonly type: Prisma.DatetimesGetPayload } +interface BoolsGetPayload extends HKT { + readonly _A?: boolean | null | undefined | Prisma.BoolsArgs + readonly type: Prisma.BoolsGetPayload +} + export const tableSchemas = { items: { fields: new Map([ @@ -1603,6 +1836,43 @@ export const tableSchemas = { Prisma.DatetimesScalarFieldEnum, DatetimesGetPayload >, + bools: { + fields: new Map([ + [ + "id", + "TEXT" + ], + [ + "b", + "BOOL" + ] + ]), + relations: [ + ], + modelSchema: (BoolsCreateInputSchema as any) + .partial() + .or((BoolsUncheckedCreateInputSchema as any).partial()), + createSchema: BoolsCreateArgsSchema, + createManySchema: BoolsCreateManyArgsSchema, + findUniqueSchema: BoolsFindUniqueArgsSchema, + findSchema: BoolsFindFirstArgsSchema, + updateSchema: BoolsUpdateArgsSchema, + updateManySchema: BoolsUpdateManyArgsSchema, + upsertSchema: BoolsUpsertArgsSchema, + deleteSchema: BoolsDeleteArgsSchema, + deleteManySchema: BoolsDeleteManyArgsSchema + } as TableSchema< + z.infer, + Prisma.BoolsCreateArgs['data'], + Prisma.BoolsUpdateArgs['data'], + Prisma.BoolsFindFirstArgs['select'], + Prisma.BoolsFindFirstArgs['where'], + Prisma.BoolsFindUniqueArgs['where'], + never, + Prisma.BoolsFindFirstArgs['orderBy'], + Prisma.BoolsScalarFieldEnum, + BoolsGetPayload + >, } export const schema = new DbSchema(tableSchemas, migrations) diff --git a/e2e/satellite_client/src/prisma/schema.prisma b/e2e/satellite_client/src/prisma/schema.prisma index 9d6ea640a8..1d4ed76101 100644 --- a/e2e/satellite_client/src/prisma/schema.prisma +++ b/e2e/satellite_client/src/prisma/schema.prisma @@ -42,4 +42,9 @@ model Datetimes { id String @id d DateTime @db.Date t DateTime @db.Time(3) +} + +model Bools { + id String @id + b Boolean? } \ No newline at end of file diff --git a/e2e/tests/03.15_node_satellite_can_sync_booleans.lux b/e2e/tests/03.15_node_satellite_can_sync_booleans.lux index 60bb098e6b..483627fc86 100644 --- a/e2e/tests/03.15_node_satellite_can_sync_booleans.lux +++ b/e2e/tests/03.15_node_satellite_can_sync_booleans.lux @@ -26,18 +26,13 @@ ??INSERT 0 3 [shell satellite_1] - [invoke node_await_get_from_table "bools" "003"] + [invoke node_await_get_bool "001" "true"] + [invoke node_await_get_bool "002" "false"] + [invoke node_await_get_bool "003" "null"] - ??id: '001' - ??b: 1 - - ??id: '002' - ??b: 0 - - ??id: '003' - ??b: null - - [invoke node_await_insert_extended_into "bools" "{id: '004', b: 1}"] + !await client.write_bool(db, '004', true) + ?{ id: '004', b: true } + ?$node [shell pg_1] [invoke wait-for "SELECT * FROM public.bools;" "004" 10 $psql] @@ -55,18 +50,10 @@ [invoke node_await_table "bools"] [invoke node_sync_table "bools"] - [invoke node_await_get_from_table "bools" "004"] - ??id: '001' - ??b: 1 - - ??id: '002' - ??b: 0 - - ??id: '003' - ??b: null - - ??id: '004' - ??b: 1 + [invoke node_await_get_bool "001" "true"] + [invoke node_await_get_bool "002" "false"] + [invoke node_await_get_bool "003" "null"] + [invoke node_await_get_bool "004" "true"] [cleanup] [invoke teardown] diff --git a/e2e/tests/_satellite_macros.luxinc b/e2e/tests/_satellite_macros.luxinc index 2f277888f1..c042f9f983 100644 --- a/e2e/tests/_satellite_macros.luxinc +++ b/e2e/tests/_satellite_macros.luxinc @@ -39,6 +39,10 @@ [invoke wait-for "await client.get_rows(db, '${table}')" "${match}" 10 $node] [endmacro] +[macro node_await_get_bool id expected_bool] + [invoke wait-for "await client.get_bool(db, '${id}')" "${expected_bool}" 10 $node] +[endmacro] + [macro node_await_get_timestamps match] [invoke wait-for "await client.get_timestamps(db)" "${match}" 10 $node] [endmacro] diff --git a/generator/src/functions/tableDescriptionWriters/writeTableSchemas.ts b/generator/src/functions/tableDescriptionWriters/writeTableSchemas.ts index a38a051996..2dc45048c1 100644 --- a/generator/src/functions/tableDescriptionWriters/writeTableSchemas.ts +++ b/generator/src/functions/tableDescriptionWriters/writeTableSchemas.ts @@ -109,7 +109,7 @@ function pgType(field: ExtendedDMMFField, modelName: string): string { case 'Int': return 'INT4' case 'Boolean': - return 'BOOLEAN' + return 'BOOL' case 'DateTime': return dateTimeToPg(attributes, field.name, modelName) case 'BigInt':