Skip to content

Commit 53924bf

Browse files
committed
drizzle-driver: fix rebased beta rewrite
1 parent 9f4b337 commit 53924bf

2 files changed

Lines changed: 92 additions & 49 deletions

File tree

packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts

Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,19 @@ import {
1515

1616
type PreparedQueryConfig = Omit<PreparedQueryConfigBase, 'statement' | 'run'>;
1717

18-
/**
19-
* Callback which uses a LockContext for database operations.
20-
*/
2118
export type LockCallback<T> = (ctx: LockContext) => Promise<T>;
2219

23-
/**
24-
* Provider for specific database contexts.
25-
* Handlers are provided a context to the provided callback.
26-
* This does not necessarily need to acquire a database lock for each call.
27-
* Calls might use the same lock context for multiple operations.
28-
* The read/write context may relate to a single read OR write context.
29-
*/
3020
export type ContextProvider = {
3121
useReadContext: <T>(fn: LockCallback<T>) => Promise<T>;
3222
useWriteContext: <T>(fn: LockCallback<T>) => Promise<T>;
3323
};
3424

25+
type ResultMapper = (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown;
26+
type RelationalResultMapper = (
27+
rows: Record<string, unknown>[],
28+
mapColumnValue?: (value: unknown) => unknown
29+
) => unknown;
30+
3531
export class PowerSyncSQLitePreparedQuery<
3632
T extends PreparedQueryConfig = PreparedQueryConfig
3733
> extends SQLitePreparedQuery<{
@@ -46,25 +42,62 @@ export class PowerSyncSQLitePreparedQuery<
4642

4743
private readOnly = false;
4844

45+
constructor(
46+
contextProvider: ContextProvider,
47+
query: Query,
48+
logger: Logger,
49+
fields: SelectedFieldsOrdered | undefined,
50+
executeMethod: SQLiteExecuteMethod,
51+
isResponseInArrayMode: boolean,
52+
customResultMapper?: ResultMapper,
53+
cache?: Cache | undefined,
54+
queryMetadata?:
55+
| {
56+
type: 'select' | 'update' | 'delete' | 'insert';
57+
tables: string[];
58+
}
59+
| undefined,
60+
cacheConfig?: WithCacheConfig | undefined,
61+
relationalQueryMode?: false
62+
);
63+
constructor(
64+
contextProvider: ContextProvider,
65+
query: Query,
66+
logger: Logger,
67+
fields: SelectedFieldsOrdered | undefined,
68+
executeMethod: SQLiteExecuteMethod,
69+
isResponseInArrayMode: boolean,
70+
customResultMapper: RelationalResultMapper,
71+
cache: Cache | undefined,
72+
queryMetadata:
73+
| {
74+
type: 'select' | 'update' | 'delete' | 'insert';
75+
tables: string[];
76+
}
77+
| undefined,
78+
cacheConfig: WithCacheConfig | undefined,
79+
relationalQueryMode: true
80+
);
4981
constructor(
5082
private contextProvider: ContextProvider,
5183
query: Query,
5284
private logger: Logger,
5385
private fields: SelectedFieldsOrdered | undefined,
5486
executeMethod: SQLiteExecuteMethod,
5587
private _isResponseInArrayMode: boolean,
56-
private customResultMapper?: (rows: unknown[][]) => unknown,
88+
private customResultMapper?: ResultMapper | RelationalResultMapper,
5789
cache?: Cache | undefined,
5890
queryMetadata?:
5991
| {
6092
type: 'select' | 'update' | 'delete' | 'insert';
6193
tables: string[];
6294
}
6395
| undefined,
64-
cacheConfig?: WithCacheConfig | undefined
96+
cacheConfig?: WithCacheConfig | undefined,
97+
private relationalQueryMode = false
6598
) {
6699
super('async', executeMethod, query, cache, queryMetadata, cacheConfig);
67-
this.readOnly = queryMetadata?.type == 'select';
100+
this.readOnly = queryMetadata?.type == 'select' || relationalQueryMode;
68101
}
69102

70103
async run(placeholderValues?: Record<string, unknown>): Promise<QueryResult> {
@@ -85,10 +118,20 @@ export class PowerSyncSQLitePreparedQuery<
85118
});
86119
}
87120

121+
if (customResultMapper && this.relationalQueryMode) {
122+
const params = fillPlaceholders(query.params, placeholderValues ?? {});
123+
logger.logQuery(query.sql, params);
124+
const relationalResultMapper = customResultMapper as RelationalResultMapper;
125+
return await this.useContext(async (ctx) => {
126+
const rows = (await ctx.getAll(this.query.sql, params)) as Record<string, unknown>[];
127+
return relationalResultMapper(rows) as T['all'];
128+
});
129+
}
130+
88131
const rows = (await this.values(placeholderValues)) as unknown[][];
89132
if (customResultMapper) {
90-
const mapped = customResultMapper(rows) as T['all'];
91-
return mapped;
133+
const resultMapper = customResultMapper as ResultMapper;
134+
return resultMapper(rows) as T['all'];
92135
}
93136
return rows.map((row) => mapResultRow(fields!, row, (this as any).joinsNotNullableMap));
94137
}
@@ -105,6 +148,17 @@ export class PowerSyncSQLitePreparedQuery<
105148
});
106149
}
107150

151+
if (customResultMapper && this.relationalQueryMode) {
152+
const relationalResultMapper = customResultMapper as RelationalResultMapper;
153+
return this.useContext(async (ctx) => {
154+
const row = (await ctx.get(this.query.sql, params)) as Record<string, unknown> | undefined;
155+
if (!row) {
156+
return undefined as T['get'];
157+
}
158+
return relationalResultMapper([row]) as T['get'];
159+
});
160+
}
161+
108162
const rows = (await this.values(placeholderValues)) as unknown[][];
109163
const row = rows[0];
110164

@@ -113,7 +167,8 @@ export class PowerSyncSQLitePreparedQuery<
113167
}
114168

115169
if (customResultMapper) {
116-
return customResultMapper(rows) as T['get'];
170+
const resultMapper = customResultMapper as ResultMapper;
171+
return resultMapper(rows) as T['get'];
117172
}
118173

119174
return mapResultRow(fields!, row, joinsNotNullableMap);
@@ -141,17 +196,11 @@ export class PowerSyncSQLitePreparedQuery<
141196
}
142197
}
143198

144-
/**
145-
* Maps a flat array of database row values to a result object based on the provided column definitions.
146-
* It reconstructs the hierarchical structure of the result by following the specified paths for each field.
147-
* It also handles nullification of nested objects when joined tables are nullable.
148-
*/
149199
export function mapResultRow<TResult>(
150200
columns: SelectedFieldsOrdered,
151201
row: unknown[],
152202
joinsNotNullableMap: Record<string, boolean> | undefined
153203
): TResult {
154-
// Key -> nested object key, value -> table name if all fields in the nested object are from the same table, false otherwise
155204
const nullifyMap: Record<string, string | false> = {};
156205

157206
const result = columns.reduce<Record<string, any>>((result, { path, field }, columnIndex) => {
@@ -178,10 +227,7 @@ export function mapResultRow<TResult>(
178227
return result as TResult;
179228
}
180229

181-
/**
182-
* Determines the appropriate decoder for a given field.
183-
*/
184-
function getDecoder(field: SQLiteColumn | SQL<unknown> | SQL.Aliased): DriverValueDecoder<unknown, unknown> {
230+
function getDecoder(field: any): DriverValueDecoder<unknown, unknown> {
185231
if (is(field, Column)) {
186232
return field;
187233
} else if (is(field, SQL)) {
@@ -204,15 +250,15 @@ function updateNullifyMap(
204250

205251
const objectName = path[0]!;
206252
if (!(objectName in nullifyMap)) {
207-
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
208-
} else if (typeof nullifyMap[objectName] === 'string' && nullifyMap[objectName] !== getTableName(field.table)) {
253+
nullifyMap[objectName] = value === null ? getTableName((field as any).table) : false;
254+
} else if (
255+
typeof nullifyMap[objectName] === 'string' &&
256+
nullifyMap[objectName] !== getTableName((field as any).table)
257+
) {
209258
nullifyMap[objectName] = false;
210259
}
211260
}
212261

213-
/**
214-
* Nullify all nested objects from nullifyMap that are nullable
215-
*/
216262
function applyNullifyMap(
217263
result: Record<string, any>,
218264
nullifyMap: Record<string, string | false>,

packages/drizzle-driver/src/utils/schema.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ import {
2323
type TableConfig
2424
} from 'drizzle-orm/sqlite-core';
2525

26-
type PowerSyncColumnValue<T> = null extends T ? PowerSyncNonNullColumnValue<Exclude<T, null>> | null : PowerSyncNonNullColumnValue<T>;
26+
type PowerSyncColumnValue<T> = null extends T
27+
? PowerSyncNonNullColumnValue<Exclude<T, null>> | null
28+
: PowerSyncNonNullColumnValue<T>;
2729

2830
type PowerSyncNonNullColumnValue<T> = T extends number | string ? T : T extends boolean | Date ? number : string;
2931

30-
type DrizzleTableColumns<T extends SQLiteTableWithColumns<any>> = T extends SQLiteTableWithColumns<infer TConfig>
31-
? TConfig['columns']
32-
: never;
32+
type DrizzleTableColumns<T extends SQLiteTableWithColumns<any>> =
33+
T extends SQLiteTableWithColumns<infer TConfig> ? TConfig['columns'] : never;
3334

3435
type DrizzleColumnData<T> = T extends { _: { data: infer TData } } ? TData : never;
3536

@@ -115,7 +116,6 @@ export type DrizzleTableWithPowerSyncOptions = {
115116
options?: DrizzleTablePowerSyncOptions;
116117
};
117118

118-
<<<<<<< HEAD
119119
type DrizzleSchemaEntry = SQLiteTableWithColumns<any> | DrizzleTableWithPowerSyncOptions | Record<string, unknown>;
120120

121121
export type TableName<T> = T extends { _: { name: infer TName extends string } }
@@ -125,33 +125,32 @@ export type TableName<T> = T extends { _: { name: infer TName extends string } }
125125
: never;
126126

127127
export type TablesFromSchemaEntries<T> = {
128-
[K in keyof T as T[K] extends Relations
129-
? never
130-
: T[K] extends SQLiteTableWithColumns<any> | DrizzleTableWithPowerSyncOptions
131-
? TableName<T[K]>
132-
: never]: T[K] extends SQLiteTableWithColumns<any>
128+
[K in keyof T as T[K] extends SQLiteTableWithColumns<any> | DrizzleTableWithPowerSyncOptions
129+
? TableName<T[K]>
130+
: never]: T[K] extends SQLiteTableWithColumns<any>
133131
? Table<Expand<ExtractPowerSyncColumns<T[K]>>>
134132
: T[K] extends DrizzleTableWithPowerSyncOptions
135133
? Table<Expand<ExtractPowerSyncColumns<T[K]['tableDefinition']>>>
136134
: never;
137135
};
138136

139-
function toPowerSyncTables<
140-
T extends Record<string, SQLiteTableWithColumns<any> | Relations | DrizzleTableWithPowerSyncOptions>
141-
>(schemaEntries: T, options?: DrizzleAppSchemaOptions) {
137+
function toPowerSyncTables<T extends Record<string, DrizzleSchemaEntry>>(
138+
schemaEntries: T,
139+
options?: DrizzleAppSchemaOptions
140+
) {
142141
const casingCache = options?.casing ? new CasingCache(options?.casing) : undefined;
143142

144143
const tables: Record<string, Table> = {};
145144
for (const schemaEntry of Object.values(schemaEntries)) {
146-
let maybeTable: SQLiteTableWithColumns<any> | Relations | undefined = undefined;
145+
let maybeTable: SQLiteTableWithColumns<any> | undefined = undefined;
147146
let maybeOptions: DrizzleTablePowerSyncOptions | undefined = undefined;
148147

149148
if (typeof schemaEntry === 'object' && 'tableDefinition' in schemaEntry) {
150149
const tableWithOptions = schemaEntry as DrizzleTableWithPowerSyncOptions;
151150
maybeTable = tableWithOptions.tableDefinition;
152151
maybeOptions = tableWithOptions.options;
153152
} else {
154-
maybeTable = schemaEntry;
153+
maybeTable = schemaEntry as SQLiteTableWithColumns<any> | undefined;
155154
}
156155

157156
if (isTable(maybeTable)) {
@@ -169,9 +168,7 @@ function toPowerSyncTables<
169168
export type DrizzleAppSchemaOptions = {
170169
casing?: Casing;
171170
};
172-
export class DrizzleAppSchema<
173-
T extends Record<string, SQLiteTableWithColumns<any> | Relations | DrizzleTableWithPowerSyncOptions>
174-
> extends Schema {
171+
export class DrizzleAppSchema<T extends Record<string, DrizzleSchemaEntry>> extends Schema {
175172
constructor(drizzleSchema: T, options?: DrizzleAppSchemaOptions) {
176173
super(toPowerSyncTables(drizzleSchema, options));
177174
// This is just used for typing

0 commit comments

Comments
 (0)