Skip to content

Commit 85d8635

Browse files
committed
remove prettier implement nested queries
1 parent 82705e3 commit 85d8635

File tree

10 files changed

+669
-308
lines changed

10 files changed

+669
-308
lines changed

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"dbtype",
55
"execrows",
66
"sqlc",
7-
"tstype"
7+
"sqln",
8+
"tstype",
9+
"unflatten"
810
]
911
}

example/src/index.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { sqlc, type InferParam, type InferRow } from './sqlc.ts';
1+
import { sqlc, sqln} from './sqlc.ts';
22
import { Pool, type PoolClient } from 'pg';
33
import type { UUID } from './types.ts';
44

@@ -27,18 +27,42 @@ export class CustomerService {
2727
};
2828

2929
public get_by_id = async (customer_id: string) => {
30-
return this.with_client((client) => {
30+
return this.with_client(async (client) => {
3131
// comment is not necessary, but it's nice to have
3232
// to have syntax highlighting and formatting
33-
return sqlc(`
33+
const [data = null] = await sqln(`
3434
SELECT
35-
customer_id,
36-
store_id
37-
FROM customer
35+
c.customer_id AS id,
36+
c.first_name,
37+
c.last_name,
38+
s.manager_staff_id AS "store.manager_staff_id",
39+
a.address_id AS "store.address.id",
40+
a.address AS "store.address.address",
41+
a.address2 AS "store.address.address2"
42+
FROM customer AS c
43+
JOIN store AS s ON c.store_id = s.store_id
44+
JOIN address AS a ON c.address_id = a.address_id
3845
WHERE customer_id = @customer_id
3946
`).exec(client, {
4047
customer_id: customer_id as UUID,
4148
});
49+
50+
// Automatically unflattens the result
51+
// {
52+
// id: UUID;
53+
// first_name: string;
54+
// last_name: string;
55+
// store: {
56+
// manager_staff_id: number;
57+
// address: {
58+
// id: number;
59+
// address: string;
60+
// address2: string | null;
61+
// };
62+
// };
63+
// }[]
64+
65+
return data;
4266
});
4367
};
4468

@@ -79,10 +103,6 @@ export class CustomerService {
79103
ORDER BY spending_rank
80104
`);
81105

82-
// you can get the types from the query
83-
type QueryType = InferRow<typeof query>;
84-
type QueryParam = InferParam<typeof query>;
85-
86106
return this.with_client((client) => query.exec(client));
87107
};
88108
}

example/src/sqlc.ts

Lines changed: 156 additions & 158 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,9 @@
3232
"chokidar": "^4.0.3",
3333
"commander": "^13.1.0",
3434
"ora": "^8.2.0",
35-
"prettier": "3.4.2",
3635
"typescript": "^5.7.3"
3736
},
3837
"devDependencies": {
39-
"@types/node": "^22.13.1"
38+
"@types/node": "^22.13.2"
4039
}
4140
}

src/generator.ts

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import { execFile } from 'node:child_process';
22
import crypto from 'node:crypto';
33
import fs from 'node:fs/promises';
44
import path from 'node:path';
5-
import prettier from 'prettier';
5+
import ts from 'typescript';
66
import util from 'util';
77
import { generate_types, render_template } from './render.ts';
8-
import type { Config, SqlcResult } from './types.ts';
9-
import ts from 'typescript';
8+
import type { Config, SqlcResult, SqlQuery, SqlQueryParseResult } from './types.ts';
109

1110
const execFileAsync = util.promisify(execFile);
1211

@@ -18,14 +17,14 @@ export const generate = async (config: Config) => {
1817
const exec_result = await exec_sqlc(config);
1918

2019
if (exec_result.success) {
21-
const { queries_content, schema_types_content } = generate_types({
20+
const { rendered_queries, schema_types_content } = generate_types({
2221
sqlc_result: exec_result.result,
2322
queries,
2423
config,
2524
});
2625

2726
await render_write({
28-
queries_content,
27+
rendered_queries,
2928
schema_types_content,
3029
result_path: config.output,
3130
config,
@@ -34,7 +33,10 @@ export const generate = async (config: Config) => {
3433
return { queries: queries.size };
3534
} else {
3635
await render_write({
37-
queries_content: '// Unable to generate queries: ' + exec_result.result,
36+
rendered_queries: {
37+
flat: '// Unable to generate queries: ' + exec_result.result,
38+
nested: '// Unable to generate queries: ' + exec_result.result,
39+
},
3840
result_path: config.output,
3941
config,
4042
});
@@ -49,38 +51,26 @@ export const generate = async (config: Config) => {
4951
};
5052

5153
export const render_write = async ({
52-
queries_content,
54+
rendered_queries,
5355
schema_types_content,
5456
result_path,
5557
config,
5658
}: {
57-
queries_content: string;
59+
rendered_queries: {
60+
flat: string;
61+
nested: string;
62+
};
5863
schema_types_content?: string;
5964
result_path: string;
6065
config: Pick<Config, 'imports'>;
6166
}) => {
6267
const file = render_template({
63-
queries_content,
68+
rendered_queries,
6469
schema_types_content,
6570
imports: config.imports,
6671
});
6772

68-
const formatted = await prettier.format(file, {
69-
parser: 'typescript',
70-
printWidth: 128,
71-
tabWidth: 4,
72-
useTabs: false,
73-
semi: true,
74-
singleQuote: true,
75-
jsxSingleQuote: true,
76-
trailingComma: 'all',
77-
bracketSpacing: true,
78-
bracketSameLine: true,
79-
arrowParens: 'always',
80-
endOfLine: 'lf',
81-
});
82-
83-
await fs.writeFile(result_path, formatted);
73+
await fs.writeFile(result_path, file);
8474
};
8575

8676
export const exec_sqlc = async ({ tmp_dir }: Pick<Config, 'tmp_dir' | 'root'>) => {
@@ -154,22 +144,22 @@ export const prepare_tmp_dir = async ({ schema, tmp_dir }: Pick<Config, 'root' |
154144
};
155145

156146
export async function scan_files({ root, include, tmp_dir }: Pick<Config, 'root' | 'include' | 'tmp_dir'>) {
157-
const queries = new Map<string, string>();
147+
const queries = new Map<string, SqlQuery>();
158148
const queries_file = await fs.open(path.join(tmp_dir, 'queries.sql'), 'w+');
159149

160150
try {
161151
for await (const file of fs.glob(include, { cwd: root })) {
162152
const content = await fs.readFile(path.join(root, file), 'utf8');
163153

164-
for (const query of extract_sql(content)) {
165-
if (query.success) {
166-
const { name, content } = render_query(query.sql);
154+
for (const result of extract_sql(content)) {
155+
if (result.success) {
156+
const { name, content } = render_query(result.query.sql);
167157
if (!queries.has(name)) {
168158
await queries_file.appendFile(`${content}\n\n`);
169-
queries.set(name, query.sql);
159+
queries.set(name, result.query);
170160
}
171161
} else {
172-
console.error(query.error);
162+
console.error(result.error);
173163
}
174164
}
175165
}
@@ -180,10 +170,8 @@ export async function scan_files({ root, include, tmp_dir }: Pick<Config, 'root'
180170
return queries;
181171
}
182172

183-
type SqlResult = { success: true; sql: string } | { success: false; error: string };
184-
185-
export const extract_sql = (content: string): SqlResult[] => {
186-
const results: SqlResult[] = [];
173+
export const extract_sql = (content: string): SqlQueryParseResult[] => {
174+
const results: SqlQueryParseResult[] = [];
187175

188176
try {
189177
const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
@@ -193,13 +181,13 @@ export const extract_sql = (content: string): SqlResult[] => {
193181
const identifier = node.expression;
194182

195183
// Check if it's a call to 'sqlc'
196-
if (ts.isIdentifier(identifier) && identifier.text === 'sqlc') {
184+
if (ts.isIdentifier(identifier) && (identifier.text === 'sqlc' || identifier.text === 'sqln')) {
197185
const arg = node.arguments[0];
198186

199187
if (!arg) {
200188
results.push({
201189
success: false,
202-
error: `Missing argument in sqlc call at position ${node.pos}`,
190+
error: `Missing argument in sqln call at position ${node.pos}`,
203191
});
204192
return;
205193
}
@@ -227,12 +215,24 @@ export const extract_sql = (content: string): SqlResult[] => {
227215
// Handle template literal
228216
if (ts.isNoSubstitutionTemplateLiteral(arg)) {
229217
query = arg.getText().slice(1, -1); // Remove backticks
230-
results.push({ success: true, sql: query });
218+
results.push({
219+
success: true,
220+
query: {
221+
type: identifier.text === 'sqln' ? 'nested' : 'flat',
222+
sql: query,
223+
},
224+
});
231225
}
232226
// Handle string literal
233227
else if (ts.isStringLiteral(arg)) {
234228
query = arg.getText().slice(1, -1); // Remove quotes
235-
results.push({ success: true, sql: query });
229+
results.push({
230+
success: true,
231+
query: {
232+
type: identifier.text === 'sqln' ? 'nested' : 'flat',
233+
sql: query,
234+
},
235+
});
236236
} else {
237237
results.push({
238238
success: false,

0 commit comments

Comments
 (0)