diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c356d6b..18e12870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # main +- Auto-escape all ReScript keywords in generated record field names. - Add automatic parsing of PostgreSQL check constraints to generate ReScript polyvariant types for enumeration-style constraints. Supports both `column IN (value1, value2, ...)` and `column = ANY (ARRAY[value1, value2, ...])` patterns with string and integer values. - Remove dependency on `@rescript/core` since it's not really used. diff --git a/packages/cli/src/generator.ts b/packages/cli/src/generator.ts index 87855b36..99da7b68 100644 --- a/packages/cli/src/generator.ts +++ b/packages/cli/src/generator.ts @@ -479,7 +479,34 @@ function toRescriptName(name: string): string { return `${name[0]?.toLowerCase() ?? ''}${name.slice(1)}`; } -const reservedReScriptWords = ['type']; +const reservedReScriptWords = [ + 'and', + 'as', + 'assert', + 'await', + 'constraint', + 'else', + 'exception', + 'external', + 'false', + 'for', + 'if', + 'in', + 'include', + 'let', + 'module', + 'mutable', + 'of', + 'open', + 'private', + 'rec', + 'switch', + 'true', + 'try', + 'type', + 'when', + 'while', +]; function getFieldName(fieldName: string): string { if (reservedReScriptWords.includes(fieldName)) { diff --git a/packages/example/sql/schema.sql b/packages/example/sql/schema.sql index 6b2137ff..4051e2e6 100644 --- a/packages/example/sql/schema.sql +++ b/packages/example/sql/schema.sql @@ -76,6 +76,37 @@ CREATE TABLE dump( json_test jsonb ); +-- Commented out fields are already Postgres keywords +create table rescript_keywords_need_to_be_escaped( + id SERIAL PRIMARY KEY, + -- and text, + -- as text, + assert text, + await text, + -- constraint text, + -- else text, + exception text, + external text, + -- false text, + -- for text, + if text, + -- in text, + include text, + let text, + module text, + mutable text, + of text, + open text, + private text, + rec text, + switch text, + -- true text, + try text, + type text, + -- when text, + while text +); + INSERT INTO users (email, user_name, first_name, last_name, age) VALUES ('alex.doe@example.com', 'alexd', 'Alex', 'Doe', 35), ('jane.holmes@example.com', 'jane67', 'Jane', 'Holmes', 23), diff --git a/packages/example/src/books/Keywords.res b/packages/example/src/books/Keywords.res new file mode 100644 index 00000000..99314dd5 --- /dev/null +++ b/packages/example/src/books/Keywords.res @@ -0,0 +1,4 @@ +let query = %sql.one(` + /* @name Keywords */ + select * from rescript_keywords_need_to_be_escaped +`) diff --git a/packages/example/src/books/Keywords__sql.gen.tsx b/packages/example/src/books/Keywords__sql.gen.tsx new file mode 100644 index 00000000..1a38c21e --- /dev/null +++ b/packages/example/src/books/Keywords__sql.gen.tsx @@ -0,0 +1,61 @@ +/* TypeScript file generated from Keywords__sql.res by genType. */ + +/* eslint-disable */ +/* tslint:disable */ + +const Keywords__sqlJS = require('./Keywords__sql.js'); + +import type {Pg_Client_t as PgTyped_Pg_Client_t} from 'pgtyped-rescript/src/res/PgTyped.gen'; + +/** 'Keywords' parameters type */ +export type keywordsParams = void; + +/** 'Keywords' return type */ +export type keywordsResult = { + readonly assert: (undefined | string); + readonly await: (undefined | string); + readonly exception: (undefined | string); + readonly external: (undefined | string); + readonly id: number; + readonly if: (undefined | string); + readonly include: (undefined | string); + readonly let: (undefined | string); + readonly module: (undefined | string); + readonly mutable: (undefined | string); + readonly of: (undefined | string); + readonly open: (undefined | string); + readonly private: (undefined | string); + readonly rec: (undefined | string); + readonly switch: (undefined | string); + readonly try: (undefined | string); + readonly type: (undefined | string); + readonly while: (undefined | string) +}; + +/** 'Keywords' query type */ +export type keywordsQuery = { readonly params: keywordsParams; readonly result: keywordsResult }; + +/** Returns an array of all matched results. */ +export const Keywords_many: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise = Keywords__sqlJS.Keywords.many as any; + +/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ +export const Keywords_one: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise<(undefined | keywordsResult)> = Keywords__sqlJS.Keywords.one as any; + +/** Returns exactly 1 result. Raises `Exn.t` (with an optionally provided `errorMessage`) if more or less than exactly 1 result is returned. */ +export const Keywords_expectOne: (_1:PgTyped_Pg_Client_t, _2:keywordsParams, errorMessage:(undefined | string)) => Promise = Keywords__sqlJS.Keywords.expectOne as any; + +/** Executes the query, but ignores whatever is returned by it. */ +export const Keywords_execute: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise = Keywords__sqlJS.Keywords.execute as any; + +export const keywords: (params:keywordsParams, client:PgTyped_Pg_Client_t) => Promise = Keywords__sqlJS.keywords as any; + +export const Keywords: { + /** Returns exactly 1 result. Raises `Exn.t` (with an optionally provided `errorMessage`) if more or less than exactly 1 result is returned. */ + expectOne: (_1:PgTyped_Pg_Client_t, _2:keywordsParams, errorMessage:(undefined | string)) => Promise; + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + one: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise<(undefined | keywordsResult)>; + /** Returns an array of all matched results. */ + many: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise; + /** Executes the query, but ignores whatever is returned by it. */ + execute: (_1:PgTyped_Pg_Client_t, _2:keywordsParams) => Promise +} = Keywords__sqlJS.Keywords as any; diff --git a/packages/example/src/books/Keywords__sql.res b/packages/example/src/books/Keywords__sql.res new file mode 100644 index 00000000..0ff2c058 --- /dev/null +++ b/packages/example/src/books/Keywords__sql.res @@ -0,0 +1,98 @@ +/** Types generated for queries found in "src/books/Keywords.res" */ +open PgTyped + + +/** 'Keywords' parameters type */ +@gentype +type keywordsParams = unit + +/** 'Keywords' return type */ +@gentype +type keywordsResult = { + @as("assert") assert_: option, + @as("await") await_: option, + @as("exception") exception_: option, + @as("external") external_: option, + id: int, + @as("if") if_: option, + @as("include") include_: option, + @as("let") let_: option, + @as("module") module_: option, + @as("mutable") mutable_: option, + @as("of") of_: option, + @as("open") open_: option, + @as("private") private_: option, + @as("rec") rec_: option, + @as("switch") switch_: option, + @as("try") try_: option, + @as("type") type_: option, + @as("while") while_: option, +} + +/** 'Keywords' query type */ +@gentype +type keywordsQuery = { + params: keywordsParams, + result: keywordsResult, +} + +%%private(let keywordsIR: IR.t = %raw(`{"usedParamSet":{},"params":[],"statement":"select * from rescript_keywords_need_to_be_escaped"}`)) + +/** + Runnable query: + ```sql +select * from rescript_keywords_need_to_be_escaped + ``` + + */ +@gentype +module Keywords: { + /** Returns an array of all matched results. */ + @gentype + let many: (PgTyped.Pg.Client.t, keywordsParams) => promise> + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + @gentype + let one: (PgTyped.Pg.Client.t, keywordsParams) => promise> + + /** Returns exactly 1 result. Raises `Exn.t` (with an optionally provided `errorMessage`) if more or less than exactly 1 result is returned. */ + @gentype + let expectOne: ( + PgTyped.Pg.Client.t, + keywordsParams, + ~errorMessage: string=? + ) => promise + + /** Executes the query, but ignores whatever is returned by it. */ + @gentype + let execute: (PgTyped.Pg.Client.t, keywordsParams) => promise +} = { + @module("pgtyped-rescript-runtime") @new external keywords: IR.t => PreparedStatement.t = "PreparedQuery"; + let query = keywords(keywordsIR) + let query = (params, ~client) => query->PreparedStatement.run(params, ~client) + + @gentype + let many = (client, params) => query(params, ~client) + + @gentype + let one = async (client, params) => switch await query(params, ~client) { + | [item] => Some(item) + | _ => None + } + + @gentype + let expectOne = async (client, params, ~errorMessage=?) => switch await query(params, ~client) { + | [item] => item + | _ => panic(errorMessage->Option.getOr("More or less than one item was returned")) + } + + @gentype + let execute = async (client, params) => { + let _ = await query(params, ~client) + } +} + +@gentype +@deprecated("Use 'Keywords.many' directly instead") +let keywords = (params, ~client) => Keywords.many(client, params) + +