Skip to content

Commit 852b899

Browse files
committed
Stricter check on transaction accessMode. Broke mapResultRow logic into smaller functions with minor comments.
1 parent f70c71b commit 852b899

File tree

3 files changed

+62
-29
lines changed

3 files changed

+62
-29
lines changed

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

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Column, DriverValueDecoder, getTableName, SQL } from 'drizzle-orm';
33
import { entityKind, is } from 'drizzle-orm/entity';
44
import type { Logger } from 'drizzle-orm/logger';
55
import { fillPlaceholders, type Query } from 'drizzle-orm/sql/sql';
6+
import { SQLiteColumn } from 'drizzle-orm/sqlite-core';
67
import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builders/select.types';
78
import {
89
type PreparedQueryConfig as PreparedQueryConfigBase,
@@ -98,6 +99,11 @@ export class PowerSyncSQLitePreparedQuery<
9899
}
99100
}
100101

102+
/**
103+
* Maps a flat array of database row values to a result object based on the provided column definitions.
104+
* It reconstructs the hierarchical structure of the result by following the specified paths for each field.
105+
* It also handles nullification of nested objects when joined tables are nullable.
106+
*/
101107
export function mapResultRow<TResult>(
102108
columns: SelectedFieldsOrdered,
103109
row: unknown[],
@@ -107,14 +113,7 @@ export function mapResultRow<TResult>(
107113
const nullifyMap: Record<string, string | false> = {};
108114

109115
const result = columns.reduce<Record<string, any>>((result, { path, field }, columnIndex) => {
110-
let decoder: DriverValueDecoder<unknown, unknown>;
111-
if (is(field, Column)) {
112-
decoder = field;
113-
} else if (is(field, SQL)) {
114-
decoder = (field as any).decoder;
115-
} else {
116-
decoder = (field.sql as any).decoder;
117-
}
116+
const decoder = getDecoder(field);
118117
let node = result;
119118
for (const [pathChunkIndex, pathChunk] of path.entries()) {
120119
if (pathChunkIndex < path.length - 1) {
@@ -126,30 +125,64 @@ export function mapResultRow<TResult>(
126125
const rawValue = row[columnIndex]!;
127126
const value = (node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue));
128127

129-
if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
130-
const objectName = path[0]!;
131-
if (!(objectName in nullifyMap)) {
132-
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
133-
} else if (
134-
typeof nullifyMap[objectName] === 'string' &&
135-
nullifyMap[objectName] !== getTableName(field.table)
136-
) {
137-
nullifyMap[objectName] = false;
138-
}
139-
}
128+
updateNullifyMap(nullifyMap, field, path, value, joinsNotNullableMap);
140129
}
141130
}
142131
return result;
143132
}, {});
144133

145-
// Nullify all nested objects from nullifyMap that are nullable
146-
if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
147-
for (const [objectName, tableName] of Object.entries(nullifyMap)) {
148-
if (typeof tableName === 'string' && !joinsNotNullableMap[tableName]) {
149-
result[objectName] = null;
150-
}
151-
}
152-
}
134+
applyNullifyMap(result, nullifyMap, joinsNotNullableMap);
153135

154136
return result as TResult;
155137
}
138+
139+
/**
140+
* Determines the appropriate decoder for a given field.
141+
*/
142+
function getDecoder(field: SQLiteColumn | SQL<unknown> | SQL.Aliased): DriverValueDecoder<unknown, unknown> {
143+
if (is(field, Column)) {
144+
return field;
145+
} else if (is(field, SQL)) {
146+
return (field as any).decoder;
147+
} else {
148+
return (field.sql as any).decoder;
149+
}
150+
}
151+
152+
function updateNullifyMap(
153+
nullifyMap: Record<string, string | false>,
154+
field: any,
155+
path: string[],
156+
value: any,
157+
joinsNotNullableMap: Record<string, boolean> | undefined
158+
): void {
159+
if (!joinsNotNullableMap || !is(field, Column) || path.length !== 2) {
160+
return;
161+
}
162+
163+
const objectName = path[0]!;
164+
if (!(objectName in nullifyMap)) {
165+
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
166+
} else if (typeof nullifyMap[objectName] === 'string' && nullifyMap[objectName] !== getTableName(field.table)) {
167+
nullifyMap[objectName] = false;
168+
}
169+
}
170+
171+
/**
172+
* Nullify all nested objects from nullifyMap that are nullable
173+
*/
174+
function applyNullifyMap(
175+
result: Record<string, any>,
176+
nullifyMap: Record<string, string | false>,
177+
joinsNotNullableMap: Record<string, boolean> | undefined
178+
): void {
179+
if (!joinsNotNullableMap || Object.keys(nullifyMap).length === 0) {
180+
return;
181+
}
182+
183+
for (const [objectName, tableName] of Object.entries(nullifyMap)) {
184+
if (typeof tableName === 'string' && !joinsNotNullableMap[tableName]) {
185+
result[objectName] = null;
186+
}
187+
}
188+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class PowerSyncSQLiteSession<
7272
): T {
7373
const { accessMode = 'read write' } = config;
7474

75-
if (accessMode == 'read only') {
75+
if (accessMode === 'read only') {
7676
return this.db.readLock(async () => this.internalTransaction(transaction, config)) as T;
7777
}
7878

packages/drizzle-driver/tests/setup/db.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const getPowerSyncDb = () => {
2626
};
2727

2828
export const getDrizzleDb = (db: AbstractPowerSyncDatabase) => {
29-
const database = wrapPowerSyncWithDrizzle(db, { schema: DrizzleSchema });
29+
const database = wrapPowerSyncWithDrizzle(db, { schema: DrizzleSchema, logger: { logQuery: () => {} } });
3030

3131
return database;
3232
};

0 commit comments

Comments
 (0)