diff --git a/CHANGELOG.md b/CHANGELOG.md index c7a78943..7163eb30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ # main +- Make sure `JSON.t` is properly stringified so JSON arrays can be passed to params expecting `JSON.t` without them being confused for regular Postgres arrays. - 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. - Add top-level literal inference for SELECT queries. When a query returns literal values with aliases (e.g., `SELECT 'success' as status, 42 as code`), PgTyped now automatically infers specific polyvariant types like `[#"success"]` and `[#42]` instead of generic `string` and `int` types. This provides better type safety and autocompletion. Also works with UNION queries where literals are consistent across all branches. +- Add support for PostgreSQL JSON population functions (`json_populate_record`, `json_populate_recordset`, `jsonb_populate_record`, `jsonb_populate_recordset`, `json_to_record`, `jsonb_to_recordset`). This supports efficient bulk operations. - Remove dependency on `@rescript/core` since it's not really used. # 2.6.0 diff --git a/RESCRIPT.md b/RESCRIPT.md index ca4da55a..0c0343a8 100644 --- a/RESCRIPT.md +++ b/RESCRIPT.md @@ -170,6 +170,75 @@ let books = await client->GetBooksByStatus.many({ This feature works seamlessly with both separate SQL files and SQL-in-ReScript modes. +## JSON Population Functions + +`pgtyped-rescript` provides support for PostgreSQL's JSON population functions, enabling type-safe conversion between JSON data and database records. + +### Supported Functions + +- **`json_populate_record`** - Converts a JSON object to a PostgreSQL record +- **`json_populate_recordset`** - Converts a JSON array to a set of PostgreSQL records +- **`jsonb_populate_record`** - Binary JSON variant of `json_populate_record` +- **`jsonb_populate_recordset`** - Binary JSON variant of `json_populate_recordset` +- **`json_to_record`** - Converts JSON to a record with explicit column definitions +- **`jsonb_to_recordset`** - Converts JSONB array to records with explicit column definitions + +### Usage Examples + +**Bulk insert with `json_populate_recordset`:** + +```sql +/* @name bulkInsertBooks */ +INSERT INTO books (name, author_id, categories, rank) +SELECT + event.name, + event.author_id, + event.categories, + event.rank +FROM json_populate_recordset(null::books, :books!) as event +RETURNING *; +``` + +**Update single record with `json_populate_record`:** + +```sql +/* @name updateBookFromJson */ +UPDATE books +SET (name, author_id, categories, rank) = ( + SELECT + r.name, + r.author_id, + r.categories, + r.rank + FROM json_populate_record(null::books, :bookData!) as r +) +WHERE id = :bookId! +RETURNING *; +``` + +**Example:** + +```rescript +let bulkInsert = %sql.many(` + INSERT INTO books (name, author_id, categories, rank) + SELECT + event.name, + event.author_id, + event.categories, + event.rank + FROM json_populate_recordset(null::books, :books!) as event + RETURNING * +`) + +// Usage with full type safety +let newBooks = await client->bulkInsert({ + books: [ + {name: "Book 1", author_id: 1, categories: ["fiction"], rank: 1}, + {name: "Book 2", author_id: 2, categories: ["sci-fi"], rank: 2} + ] +}) +``` + ## Literal Type Inference `pgtyped-rescript` automatically infers specific polyvariant types for literal values in your SQL queries, providing enhanced type safety and better development experience. diff --git a/packages/cli/src/generator.ts b/packages/cli/src/generator.ts index 8ac26ef2..73896a2a 100644 --- a/packages/cli/src/generator.ts +++ b/packages/cli/src/generator.ts @@ -11,6 +11,7 @@ import { SQLQueryAST, SQLQueryIR, TSQueryAST, + InputParamTransforms, } from '@pgtyped/parser'; import { getTypes, TypeSource } from 'pgtyped-rescript-query'; @@ -20,7 +21,10 @@ import path from 'path'; import { ParsedConfig } from './config.js'; import { TypeAllocator, TypeMapping, TypeScope } from './types.js'; import { parseCode as parseRescriptFile } from './parseRescript.js'; -import { IQueryTypes } from 'pgtyped-rescript-query/lib/actions'; +import { + IQueryTypes, + ConstraintValue, +} from 'pgtyped-rescript-query/lib/actions'; export enum ProcessingMode { SQL = 'sql-file', @@ -34,16 +38,54 @@ export interface IField { comment?: string; } -const interfaceGen = (interfaceName: string, contents: string) => - `@gentype\ntype ${interfaceName} = { -${contents} -}\n\n`; +/** + * Generates a ReScript polyvariant type from check values + * e.g., [#"value1" | #"value2" | #42] + */ +function generatePolyvariant(checkValues: ConstraintValue[]): string { + return `[${checkValues + .map((v) => { + switch (v.type) { + case 'string': + return `#"${v.value}"`; + case 'integer': + return `#${v.value}`; + } + }) + .join(' | ')}]`; +} -export function escapeComment(comment: string) { - return comment.replace(/\*\//g, '*\\/'); +/** + * Wraps a type in option<> if needed + */ +function wrapInOption(typeName: string, shouldWrap: boolean): string { + return shouldWrap ? `option<${typeName}>` : typeName; } -export const generateInterface = (interfaceName: string, fields: IField[]) => { +/** + * Wraps a type in array<> if needed + */ +function wrapInArray(typeName: string, shouldWrap: boolean): string { + return shouldWrap ? `array<${typeName}>` : typeName; +} + +/** + * Generates a ReScript record type from fields + */ +function generateRecordType( + fields: Array<{ name: string; type: string; required?: boolean }>, +): string { + const fieldStrings = fields.map((field) => { + const optional = field.required === false ? '?' : ''; + return ` ${getFieldName(field.name)}${optional}: ${field.type}`; + }); + return `{\n${fieldStrings.join(',\n')}\n}`; +} + +/** + * Generates a complete ReScript record interface with @gentype annotation + */ +function generateInterface(interfaceName: string, fields: IField[]): string { const sortedFields = fields .slice() .sort((a, b) => a.fieldName.localeCompare(b.fieldName)); @@ -54,11 +96,84 @@ export const generateInterface = (interfaceName: string, fields: IField[]) => { ` ${getFieldName(fieldName)}${optional ? '?' : ''}: ${fieldType},`, ) .join('\n'); - return interfaceGen(interfaceName, contents); -}; + return `@gentype\ntype ${interfaceName} = {\n${contents}\n}\n\n`; +} + +/** + * Generates a ReScript type alias with @gentype annotation + */ +function generateTypeAlias(typeName: string, alias: string): string { + return `@gentype\ntype ${typeName} = ${alias}\n\n`; +} + +/** + * Processes a type with check values, returning either the original type or a polyvariant + */ +function processTypeWithCheckValues( + baseTypeName: string, + checkValues?: ConstraintValue[], +): string { + if (checkValues != null && checkValues.length > 0) { + return generatePolyvariant(checkValues); + } + return baseTypeName; +} + +/** + * Converts a name to ReScript naming convention (camelCase starting with lowercase) + */ +function toRescriptName(name: string): string { + if (name == null || name.length === 0) { + return name; + } + return `${name[0]?.toLowerCase() ?? ''}${name.slice(1)}`; +} + +/** + * Handles ReScript reserved words by adding @as annotation + */ +function getFieldName(fieldName: string): string { + if (reservedReScriptWords.includes(fieldName)) { + return `@as("${fieldName}") ${fieldName}_`; + } + return fieldName; +} -export const generateTypeAlias = (typeName: string, alias: string) => - `@gentype\ntype ${typeName} = ${alias}\n\n`; +/** + * Escapes comments for ReScript + */ +function escapeComment(comment: string): string { + return comment.replace(/\*\//g, '*\\/'); +} + +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', +]; type ParsedQuery = | { @@ -75,7 +190,10 @@ export async function queryToTypeDeclarations( typeSource: TypeSource, types: TypeAllocator, config: ParsedConfig, -): Promise { +): Promise<{ + result: string; + inputParamTransforms: InputParamTransforms | null; +}> { let queryData; let queryName; if (parsedQuery.mode === ProcessingMode.TS) { @@ -83,12 +201,11 @@ export async function queryToTypeDeclarations( queryData = processTSQueryAST(parsedQuery.ast); } else { queryName = pascalCase(parsedQuery.ast.name); - queryData = processSQLQueryIR(queryASTToIR(parsedQuery.ast)); + queryData = processSQLQueryIR(queryASTToIR(parsedQuery.ast, null)); } const typeData = await typeSource(queryData); const interfaceName = toRescriptName(pascalCase(queryName)); - const interfacePrefix = ''; const typeError = 'errorCode' in typeData; const hasAnonymousColumns = @@ -116,51 +233,69 @@ export async function queryToTypeDeclarations( explanation = `Query contains an anonymous column. Consider giving the column an explicit name.`; } - const returnInterface = generateTypeAlias( - `${interfacePrefix}${interfaceName}Result`, - 'unit', - ); - const paramInterface = generateTypeAlias( - `${interfacePrefix}${interfaceName}Params`, - 'unit', - ); + const returnInterface = generateTypeAlias(`${interfaceName}Result`, 'unit'); + const paramInterface = generateTypeAlias(`${interfaceName}Params`, 'unit'); const resultErrorComment = `/** Query '${queryName}' is invalid, so its result is assigned type 'unit'.\n * ${explanation} */\n`; const paramErrorComment = `/** Query '${queryName}' is invalid, so its parameters are assigned type 'unit'.\n * ${explanation} */\n`; - return `${resultErrorComment}${returnInterface}${paramErrorComment}${paramInterface}`; + return { + result: `${resultErrorComment}${returnInterface}${paramErrorComment}${paramInterface}`, + inputParamTransforms: null, + }; } - const { returnTypes, paramMetadata } = typeData; + const { returnTypes, paramMetadata, inputTypes } = typeData; const returnFieldTypes: IField[] = []; const paramFieldTypes: IField[] = []; const records: string[] = []; + // Generate input type records + const inputTypeNames: Record = {}; + const inputParamTransforms: InputParamTransforms = {}; + for (const [_, inputTypeInfo] of Object.entries(inputTypes || {})) { + const recordTypeName = `${interfaceName}_${inputTypeInfo.tableName}InputType`; + inputTypeNames[inputTypeInfo.assignedIndex] = recordTypeName; + if (inputTypeInfo.stringify) { + inputParamTransforms[inputTypeInfo.assignedIndex] = { + type: 'stringify', + }; + } + + const inputFieldTypes: IField[] = []; + + for (const field of inputTypeInfo.fields) { + const baseTypeName = types.use(field.type, TypeScope.Parameter); + const tsTypeName = processTypeWithCheckValues( + baseTypeName, + field.checkValues, + ); + + inputFieldTypes.push({ + fieldName: config.camelCaseColumnNames + ? camelCase(field.columnName) + : field.columnName, + fieldType: tsTypeName, + optional: field.optional, + comment: field.comment, + }); + } + + const inputRecord = generateInterface(recordTypeName, inputFieldTypes); + records.push(inputRecord); + } + returnTypes.forEach( ({ returnName, type, nullable, comment, checkValues }) => { - let tsTypeName = types.use(type, TypeScope.Return); - - if (checkValues != null && checkValues.length > 0) { - tsTypeName = `[${checkValues - .map((v) => { - switch (v.type) { - case 'string': - return `#"${v.value}"`; - case 'integer': - return `#${v.value}`; - } - }) - .join(' | ')}]`; - } + const baseTypeName = types.use(type, TypeScope.Return); + let tsTypeName = processTypeWithCheckValues(baseTypeName, checkValues); const lastCharacter = returnName[returnName.length - 1]; // Checking for type hints const addNullability = lastCharacter === '?'; const removeNullability = lastCharacter === '!'; - if ( - (addNullability || nullable || nullable == null) && - !removeNullability - ) { - tsTypeName = 'option<' + tsTypeName + '>'; - } + const shouldWrapInOption = + (addNullability || nullable || nullable == null) && !removeNullability; + + tsTypeName = wrapInOption(tsTypeName, shouldWrapInOption); if (addNullability || removeNullability) { returnName = returnName.slice(0, -1); @@ -178,11 +313,29 @@ export async function queryToTypeDeclarations( const { params } = paramMetadata; for (const param of paramMetadata.mapping) { - if ( + if (param.type === 'inputTypeReference') { + const inputTypeInfo = inputTypes?.[param.assignedIndex]; + if (inputTypeInfo) { + const recordTypeName = inputTypeNames[param.assignedIndex]; + if (recordTypeName) { + paramFieldTypes.push({ + fieldName: param.name, + fieldType: wrapInArray(recordTypeName, inputTypeInfo.multi), + }); + continue; + } + } + // Fallback if input type info is not available + paramFieldTypes.push({ + fieldName: param.name, + fieldType: 'unknown', + }); + } else if ( param.type === ParameterTransform.Scalar || param.type === ParameterTransform.Spread ) { const isArray = param.type === ParameterTransform.Spread; + const assignedIndex = param.assignedIndex instanceof Array ? param.assignedIndex[0] @@ -198,31 +351,39 @@ export async function queryToTypeDeclarations( const optional = param.type === ParameterTransform.Scalar && !param.required; + tsTypeName = wrapInArray(tsTypeName, isArray); + + // Make sure any JSON.t or array of JSON is stringified, since Postgres otherwise might + // treat it as a Postgres array. + if ( + tsTypeName === 'JSON.t' || + tsTypeName === 'array' || + tsTypeName === 'arrayJSON_t' // TODO(rescript) Should get rid of these intermediate types if possible. + ) { + inputParamTransforms[param.assignedIndex.toString()] = { + type: 'stringify', + }; + } + paramFieldTypes.push({ optional, fieldName: param.name, - fieldType: isArray ? `array<${tsTypeName}>` : tsTypeName, + fieldType: tsTypeName, }); } else { const isArray = param.type === ParameterTransform.PickSpread; - let fieldType = Object.values(param.dict) - .map((p) => { - const paramType = types.use( - params[p.assignedIndex - 1], - TypeScope.Parameter, - ); - return p.required - ? ` ${getFieldName(p.name)}: ${paramType}` - : ` ${getFieldName(p.name)}?: ${paramType}`; - }) - .join(',\n'); - fieldType = `{\n${fieldType}\n}\n`; - const name = `${interfacePrefix}${interfaceName}Params_${param.name}`; + const recordFields = Object.values(param.dict).map((p) => ({ + name: p.name, + type: types.use(params[p.assignedIndex - 1], TypeScope.Parameter), + required: p.required, + })); + + let fieldType = generateRecordType(recordFields); + const name = `${interfaceName}Params_${param.name}`; records.push(`@gentype\ntype ${name} = ${fieldType}`); fieldType = name; - if (isArray) { - fieldType = `array<${fieldType}>`; - } + fieldType = wrapInArray(fieldType, isArray); + paramFieldTypes.push({ fieldName: param.name, fieldType, @@ -236,36 +397,36 @@ export async function queryToTypeDeclarations( // tslint:disable-next-line:no-console types.errors.forEach((err) => console.log(err)); - const resultInterfaceName = `${interfacePrefix}${interfaceName}Result`; + const resultInterfaceName = `${interfaceName}Result`; const returnTypesInterface = `/** '${queryName}' return type */\n` + (returnFieldTypes.length > 0 - ? generateInterface( - `${interfacePrefix}${interfaceName}Result`, - returnFieldTypes, - ) + ? generateInterface(`${interfaceName}Result`, returnFieldTypes) : generateTypeAlias(resultInterfaceName, 'unit')); - const paramInterfaceName = `${interfacePrefix}${interfaceName}Params`; + const paramInterfaceName = `${interfaceName}Params`; const paramTypesInterface = - `${records.join('\n')}/** '${queryName}' parameters type */\n` + + `${records.join('\n')}\n/** '${queryName}' parameters type */\n` + (paramFieldTypes.length > 0 - ? generateInterface( - `${interfacePrefix}${interfaceName}Params`, - paramFieldTypes, - ) + ? generateInterface(`${interfaceName}Params`, paramFieldTypes) : generateTypeAlias(paramInterfaceName, 'unit')); const typePairInterface = `/** '${queryName}' query type */\n` + - generateInterface(`${interfacePrefix}${interfaceName}Query`, [ + generateInterface(`${interfaceName}Query`, [ { fieldName: 'params', fieldType: paramInterfaceName }, { fieldName: 'result', fieldType: resultInterfaceName }, ]); - return [paramTypesInterface, returnTypesInterface, typePairInterface].join( - '', - ); + return { + result: [paramTypesInterface, returnTypesInterface, typePairInterface].join( + '', + ), + inputParamTransforms: + Object.keys(inputParamTransforms).length > 0 + ? inputParamTransforms + : null, + }; } type ITypedQuery = @@ -317,18 +478,19 @@ async function generateTypedecsFromFile( let typedQuery: ITypedQuery; const sqlQueryAST = queryAST as SQLQueryAST; - const result = await queryToTypeDeclarations( + const { result, inputParamTransforms } = await queryToTypeDeclarations( { ast: sqlQueryAST, mode: ProcessingMode.SQL }, typeSource, types, config, ); + const ir = queryASTToIR(sqlQueryAST, inputParamTransforms); typedQuery = { mode: 'sql' as const, query: { name: camelCase(sqlQueryAST.name), ast: sqlQueryAST, - ir: queryASTToIR(sqlQueryAST), + ir, paramTypeAlias: toRescriptName( `${interfacePrefix}${pascalCase(sqlQueryAST.name)}Params`, ), @@ -455,61 +617,8 @@ module ${ } } -@gentype -@deprecated("Use '${ - typeDec.query.name.slice(0, 1).toUpperCase() + typeDec.query.name.slice(1) - }.many' directly instead") -let ${typeDec.query.name} = (params, ~client) => ${ - typeDec.query.name.slice(0, 1).toUpperCase() + typeDec.query.name.slice(1) - }.many(client, params) - `; } return { declarationFileContents, typeDecs }; } - -function toRescriptName(name: string): string { - if (name == null || name.length === 0) { - return name; - } - - return `${name[0]?.toLowerCase() ?? ''}${name.slice(1)}`; -} - -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)) { - return `@as("${fieldName}") ${fieldName}_`; - } - - return fieldName; -} diff --git a/packages/example/sql/schema.sql b/packages/example/sql/schema.sql index 4051e2e6..e03049e9 100644 --- a/packages/example/sql/schema.sql +++ b/packages/example/sql/schema.sql @@ -13,6 +13,11 @@ COMMENT ON COLUMN users.age IS 'Age (in years)'; CREATE TYPE notification_type AS ENUM ('notification', 'reminder', 'deadline'); CREATE TYPE category AS ENUM ('thriller', 'science-fiction', 'novel'); +CREATE TYPE some_record as ( + id integer, + name text +); + CREATE TABLE notifications ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users, diff --git a/packages/example/src/books/BookService.res b/packages/example/src/books/BookService.res index 9fd3ae50..35139285 100644 --- a/packages/example/src/books/BookService.res +++ b/packages/example/src/books/BookService.res @@ -17,3 +17,32 @@ let booksByAuthor = (client, ~authorName) => { client->query({authorName: authorName}) } + +let insertBooks = (client, ~books) => { + let query = %sql.many(` + /* @name InsertBooks */ + insert into books ( + name, + author_id, + categories, + rank + ) + select + event.name, + event.author_id, + event.categories, + event.rank + from json_populate_recordset( + null::books, + :books! + ) as event + on conflict (id) do update set + name = excluded.name, + author_id = excluded.author_id, + categories = excluded.categories, + rank = excluded.rank + returning * + `) + + client->query({books: books}) +} diff --git a/packages/example/src/books/BookServiceParams2__sql.gen.tsx b/packages/example/src/books/BookServiceParams2__sql.gen.tsx index 1bc32cc5..6cf1bfe4 100644 --- a/packages/example/src/books/BookServiceParams2__sql.gen.tsx +++ b/packages/example/src/books/BookServiceParams2__sql.gen.tsx @@ -38,8 +38,6 @@ export const Query1_expectOne: (_1:PgTyped_Pg_Client_t, _2:query1Params, errorMe /** Executes the query, but ignores whatever is returned by it. */ export const Query1_execute: (_1:PgTyped_Pg_Client_t, _2:query1Params) => Promise = BookServiceParams2__sqlJS.Query1.execute as any; -export const query1: (params:query1Params, client:PgTyped_Pg_Client_t) => Promise = BookServiceParams2__sqlJS.query1 as any; - export const Query1: { /** 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:query1Params, errorMessage:(undefined | string)) => Promise; diff --git a/packages/example/src/books/BookServiceParams2__sql.res b/packages/example/src/books/BookServiceParams2__sql.res index ef3278cc..3bcbb37a 100644 --- a/packages/example/src/books/BookServiceParams2__sql.res +++ b/packages/example/src/books/BookServiceParams2__sql.res @@ -83,8 +83,4 @@ module Query1: { } } -@gentype -@deprecated("Use 'Query1.many' directly instead") -let query1 = (params, ~client) => Query1.many(client, params) - diff --git a/packages/example/src/books/BookServiceParams__sql.gen.tsx b/packages/example/src/books/BookServiceParams__sql.gen.tsx index baca139f..e2cde257 100644 --- a/packages/example/src/books/BookServiceParams__sql.gen.tsx +++ b/packages/example/src/books/BookServiceParams__sql.gen.tsx @@ -38,8 +38,6 @@ export const Query1_expectOne: (_1:PgTyped_Pg_Client_t, _2:query1Params, errorMe /** Executes the query, but ignores whatever is returned by it. */ export const Query1_execute: (_1:PgTyped_Pg_Client_t, _2:query1Params) => Promise = BookServiceParams__sqlJS.Query1.execute as any; -export const query1: (params:query1Params, client:PgTyped_Pg_Client_t) => Promise = BookServiceParams__sqlJS.query1 as any; - export const Query1: { /** 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:query1Params, errorMessage:(undefined | string)) => Promise; diff --git a/packages/example/src/books/BookServiceParams__sql.res b/packages/example/src/books/BookServiceParams__sql.res index 543ee54b..99dc2e53 100644 --- a/packages/example/src/books/BookServiceParams__sql.res +++ b/packages/example/src/books/BookServiceParams__sql.res @@ -83,8 +83,4 @@ module Query1: { } } -@gentype -@deprecated("Use 'Query1.many' directly instead") -let query1 = (params, ~client) => Query1.many(client, params) - diff --git a/packages/example/src/books/BookService__sql.gen.tsx b/packages/example/src/books/BookService__sql.gen.tsx index ee4d5fb1..9bc792f7 100644 --- a/packages/example/src/books/BookService__sql.gen.tsx +++ b/packages/example/src/books/BookService__sql.gen.tsx @@ -41,6 +41,29 @@ export type booksByAuthorResult = { /** 'BooksByAuthor' query type */ export type booksByAuthorQuery = { readonly params: booksByAuthorParams; readonly result: booksByAuthorResult }; +export type insertBooks_booksInputType = { + readonly author_id?: number; + readonly categories?: categoryArray; + readonly id?: number; + readonly name?: string; + readonly rank?: number +}; + +/** 'InsertBooks' parameters type */ +export type insertBooksParams = { readonly books: insertBooks_booksInputType[] }; + +/** 'InsertBooks' return type */ +export type insertBooksResult = { + readonly author_id: (undefined | number); + readonly categories: (undefined | categoryArray); + readonly id: number; + readonly name: (undefined | string); + readonly rank: (undefined | number) +}; + +/** 'InsertBooks' query type */ +export type insertBooksQuery = { readonly params: insertBooksParams; readonly result: insertBooksResult }; + /** Returns an array of all matched results. */ export const FindBookById_many: (_1:PgTyped_Pg_Client_t, _2:findBookByIdParams) => Promise = BookService__sqlJS.FindBookById.many as any; @@ -53,8 +76,6 @@ export const FindBookById_expectOne: (_1:PgTyped_Pg_Client_t, _2:findBookByIdPar /** Executes the query, but ignores whatever is returned by it. */ export const FindBookById_execute: (_1:PgTyped_Pg_Client_t, _2:findBookByIdParams) => Promise = BookService__sqlJS.FindBookById.execute as any; -export const findBookById: (params:findBookByIdParams, client:PgTyped_Pg_Client_t) => Promise = BookService__sqlJS.findBookById as any; - /** Returns an array of all matched results. */ export const BooksByAuthor_many: (_1:PgTyped_Pg_Client_t, _2:booksByAuthorParams) => Promise = BookService__sqlJS.BooksByAuthor.many as any; @@ -67,7 +88,17 @@ export const BooksByAuthor_expectOne: (_1:PgTyped_Pg_Client_t, _2:booksByAuthorP /** Executes the query, but ignores whatever is returned by it. */ export const BooksByAuthor_execute: (_1:PgTyped_Pg_Client_t, _2:booksByAuthorParams) => Promise = BookService__sqlJS.BooksByAuthor.execute as any; -export const booksByAuthor: (params:booksByAuthorParams, client:PgTyped_Pg_Client_t) => Promise = BookService__sqlJS.booksByAuthor as any; +/** Returns an array of all matched results. */ +export const InsertBooks_many: (_1:PgTyped_Pg_Client_t, _2:insertBooksParams) => Promise = BookService__sqlJS.InsertBooks.many as any; + +/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ +export const InsertBooks_one: (_1:PgTyped_Pg_Client_t, _2:insertBooksParams) => Promise<(undefined | insertBooksResult)> = BookService__sqlJS.InsertBooks.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 InsertBooks_expectOne: (_1:PgTyped_Pg_Client_t, _2:insertBooksParams, errorMessage:(undefined | string)) => Promise = BookService__sqlJS.InsertBooks.expectOne as any; + +/** Executes the query, but ignores whatever is returned by it. */ +export const InsertBooks_execute: (_1:PgTyped_Pg_Client_t, _2:insertBooksParams) => Promise = BookService__sqlJS.InsertBooks.execute as any; export const FindBookById: { /** Returns exactly 1 result. Raises `Exn.t` (with an optionally provided `errorMessage`) if more or less than exactly 1 result is returned. */ @@ -90,3 +121,14 @@ export const BooksByAuthor: { /** Executes the query, but ignores whatever is returned by it. */ execute: (_1:PgTyped_Pg_Client_t, _2:booksByAuthorParams) => Promise } = BookService__sqlJS.BooksByAuthor as any; + +export const InsertBooks: { + /** 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:insertBooksParams, 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:insertBooksParams) => Promise<(undefined | insertBooksResult)>; + /** Returns an array of all matched results. */ + many: (_1:PgTyped_Pg_Client_t, _2:insertBooksParams) => Promise; + /** Executes the query, but ignores whatever is returned by it. */ + execute: (_1:PgTyped_Pg_Client_t, _2:insertBooksParams) => Promise +} = BookService__sqlJS.InsertBooks as any; diff --git a/packages/example/src/books/BookService__sql.res b/packages/example/src/books/BookService__sql.res index 51cfb8f1..ce80366c 100644 --- a/packages/example/src/books/BookService__sql.res +++ b/packages/example/src/books/BookService__sql.res @@ -8,6 +8,7 @@ type category = [#"novel" | #"science-fiction" | #"thriller"] @gentype type categoryArray = array + /** 'FindBookById' parameters type */ @gentype type findBookByIdParams = { @@ -86,9 +87,6 @@ module FindBookById: { } } -@gentype -@deprecated("Use 'FindBookById.many' directly instead") -let findBookById = (params, ~client) => FindBookById.many(client, params) /** 'BooksByAuthor' parameters type */ @@ -171,8 +169,113 @@ module BooksByAuthor: { } } + @gentype -@deprecated("Use 'BooksByAuthor.many' directly instead") -let booksByAuthor = (params, ~client) => BooksByAuthor.many(client, params) +type insertBooks_booksInputType = { + author_id?: int, + categories?: categoryArray, + id?: int, + name?: string, + rank?: int, +} + + +/** 'InsertBooks' parameters type */ +@gentype +type insertBooksParams = { + books: array, +} + +/** 'InsertBooks' return type */ +@gentype +type insertBooksResult = { + author_id: option, + categories: option, + id: int, + name: option, + rank: option, +} + +/** 'InsertBooks' query type */ +@gentype +type insertBooksQuery = { + params: insertBooksParams, + result: insertBooksResult, +} + +%%private(let insertBooksIR: IR.t = %raw(`{"queryName":"InsertBooks","inputParamTransforms":{"1":{"type":"stringify"}},"usedParamSet":{"books":true},"params":[{"name":"books","required":true,"transform":{"type":"scalar"},"locs":[{"a":249,"b":255}]}],"statement":"insert into books (\n name, \n author_id, \n categories, \n rank\n )\n select\n event.name,\n event.author_id,\n event.categories,\n event.rank\n from json_populate_recordset(\n null::books,\n :books!\n ) as event\n on conflict (id) do update set \n name = excluded.name,\n author_id = excluded.author_id,\n categories = excluded.categories,\n rank = excluded.rank\n returning *"}`)) + +/** + Runnable query: + ```sql +insert into books ( + name, + author_id, + categories, + rank + ) + select + event.name, + event.author_id, + event.categories, + event.rank + from json_populate_recordset( + null::books, + $1 + ) as event + on conflict (id) do update set + name = excluded.name, + author_id = excluded.author_id, + categories = excluded.categories, + rank = excluded.rank + returning * + ``` + + */ +@gentype +module InsertBooks: { + /** Returns an array of all matched results. */ + @gentype + let many: (PgTyped.Pg.Client.t, insertBooksParams) => promise> + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + @gentype + let one: (PgTyped.Pg.Client.t, insertBooksParams) => 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, + insertBooksParams, + ~errorMessage: string=? + ) => promise + + /** Executes the query, but ignores whatever is returned by it. */ + @gentype + let execute: (PgTyped.Pg.Client.t, insertBooksParams) => promise +} = { + @module("pgtyped-rescript-runtime") @new external insertBooks: IR.t => PreparedStatement.t = "PreparedQuery"; + let query = insertBooks(insertBooksIR) + 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) + } +} diff --git a/packages/example/src/books/Dump__sql.gen.tsx b/packages/example/src/books/Dump__sql.gen.tsx index ae4e3453..d05fd61c 100644 --- a/packages/example/src/books/Dump__sql.gen.tsx +++ b/packages/example/src/books/Dump__sql.gen.tsx @@ -88,8 +88,6 @@ export const Dump_expectOne: (_1:PgTyped_Pg_Client_t, _2:dumpParams, errorMessag /** Executes the query, but ignores whatever is returned by it. */ export const Dump_execute: (_1:PgTyped_Pg_Client_t, _2:dumpParams) => Promise = Dump__sqlJS.Dump.execute as any; -export const dump: (params:dumpParams, client:PgTyped_Pg_Client_t) => Promise = Dump__sqlJS.dump as any; - export const Dump: { /** 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:dumpParams, errorMessage:(undefined | string)) => Promise; diff --git a/packages/example/src/books/Dump__sql.res b/packages/example/src/books/Dump__sql.res index 1769191d..9dd7bf2d 100644 --- a/packages/example/src/books/Dump__sql.res +++ b/packages/example/src/books/Dump__sql.res @@ -5,6 +5,7 @@ open PgTyped @gentype type arrayJSON_t = array + /** 'Dump' parameters type */ @gentype type dumpParams = unit @@ -98,8 +99,4 @@ module Dump: { } } -@gentype -@deprecated("Use 'Dump.many' directly instead") -let dump = (params, ~client) => Dump.many(client, params) - diff --git a/packages/example/src/books/Json.res b/packages/example/src/books/Json.res index 4394dd8b..8d8550b2 100644 --- a/packages/example/src/books/Json.res +++ b/packages/example/src/books/Json.res @@ -2,3 +2,90 @@ let query = %sql.one(` /* @name Json */ SELECT json_build_object('key', 'value') AS json_object; `) + +module JsonExtract = %sql(` + /* @name JsonExtract */ + SELECT + value->>'name' AS user_name, + value->>'id' AS user_id + FROM json_array_elements(:jsonData!::json) AS value +`) + +let jsonUnnestCast = %sql.one(` + /* @name JsonUnnestCast */ + SELECT unnest(:jsonData!::json[]) AS json_arr; +`) + +let jsonPopulateRecordQuery = %sql.one(` + /* @name JsonPopulateRecord */ + SELECT * FROM json_populate_record( + null::books, + :book! + ); +`) + +let jsonPopulateRecordsetQuery = %sql.one(` + /* @name JsonPopulateRecordset */ + insert into books ( + name, + author_id, + categories, + rank + ) + select + event.name, + event.author_id, + event.categories, + event.rank + from json_populate_recordset( + null::books, + :books! + ) as event + on conflict (id) do update set + name = excluded.name, + author_id = excluded.author_id, + categories = excluded.categories, + rank = excluded.rank + returning * +`) + +let jsonPopulateRecordsetJsonCastQuery = %sql.one(` + /* @name JsonPopulateRecordsetJsonCast */ + insert into books ( + name, + author_id, + categories, + rank + ) + select + event.name, + event.author_id, + event.categories, + event.rank + from json_populate_recordset( + null::books, + :books!::json + ) as event + on conflict (id) do update set + name = excluded.name, + author_id = excluded.author_id, + categories = excluded.categories, + rank = excluded.rank + returning * +`) + +let jsonbPopulateRecordQuery = %sql.one(` + /* @name JsonbPopulateRecord */ + SELECT * FROM jsonb_populate_record( + null::books, + :book! + ); +`) + +let jsonbPopulateRecordsetQuery = %sql.one(` + /* @name JsonbPopulateRecordset */ + SELECT * FROM jsonb_populate_recordset( + null::books, + :books! + ); +`) diff --git a/packages/example/src/books/Json__sql.gen.tsx b/packages/example/src/books/Json__sql.gen.tsx index fce68a07..23284581 100644 --- a/packages/example/src/books/Json__sql.gen.tsx +++ b/packages/example/src/books/Json__sql.gen.tsx @@ -9,6 +9,12 @@ import type {Pg_Client_t as PgTyped_Pg_Client_t} from 'pgtyped-rescript/src/res/ import type {t as JSON_t} from './JSON.gen'; +export type category = "novel" | "science-fiction" | "thriller"; + +export type arrayJSON_t = JSON_t[]; + +export type categoryArray = category[]; + /** 'Json' parameters type */ export type jsonParams = void; @@ -18,6 +24,131 @@ export type jsonResult = { readonly json_object: (undefined | JSON_t) }; /** 'Json' query type */ export type jsonQuery = { readonly params: jsonParams; readonly result: jsonResult }; +/** 'JsonExtract' parameters type */ +export type jsonExtractParams = { readonly jsonData: JSON_t }; + +/** 'JsonExtract' return type */ +export type jsonExtractResult = { readonly user_id: (undefined | string); readonly user_name: (undefined | string) }; + +/** 'JsonExtract' query type */ +export type jsonExtractQuery = { readonly params: jsonExtractParams; readonly result: jsonExtractResult }; + +/** 'JsonUnnestCast' parameters type */ +export type jsonUnnestCastParams = { readonly jsonData: arrayJSON_t }; + +/** 'JsonUnnestCast' return type */ +export type jsonUnnestCastResult = { readonly json_arr: (undefined | JSON_t) }; + +/** 'JsonUnnestCast' query type */ +export type jsonUnnestCastQuery = { readonly params: jsonUnnestCastParams; readonly result: jsonUnnestCastResult }; + +export type jsonPopulateRecord_booksInputType = { + readonly author_id?: number; + readonly categories?: categoryArray; + readonly id?: number; + readonly name?: string; + readonly rank?: number +}; + +/** 'JsonPopulateRecord' parameters type */ +export type jsonPopulateRecordParams = { readonly book: jsonPopulateRecord_booksInputType }; + +/** 'JsonPopulateRecord' return type */ +export type jsonPopulateRecordResult = { + readonly author_id: (undefined | number); + readonly categories: (undefined | categoryArray); + readonly id: (undefined | number); + readonly name: (undefined | string); + readonly rank: (undefined | number) +}; + +/** 'JsonPopulateRecord' query type */ +export type jsonPopulateRecordQuery = { readonly params: jsonPopulateRecordParams; readonly result: jsonPopulateRecordResult }; + +export type jsonPopulateRecordset_booksInputType = { + readonly author_id?: number; + readonly categories?: categoryArray; + readonly id?: number; + readonly name?: string; + readonly rank?: number +}; + +/** 'JsonPopulateRecordset' parameters type */ +export type jsonPopulateRecordsetParams = { readonly books: jsonPopulateRecordset_booksInputType[] }; + +/** 'JsonPopulateRecordset' return type */ +export type jsonPopulateRecordsetResult = { + readonly author_id: (undefined | number); + readonly categories: (undefined | categoryArray); + readonly id: number; + readonly name: (undefined | string); + readonly rank: (undefined | number) +}; + +/** 'JsonPopulateRecordset' query type */ +export type jsonPopulateRecordsetQuery = { readonly params: jsonPopulateRecordsetParams; readonly result: jsonPopulateRecordsetResult }; + +/** 'JsonPopulateRecordsetJsonCast' parameters type */ +export type jsonPopulateRecordsetJsonCastParams = { readonly books: JSON_t }; + +/** 'JsonPopulateRecordsetJsonCast' return type */ +export type jsonPopulateRecordsetJsonCastResult = { + readonly author_id: (undefined | number); + readonly categories: (undefined | categoryArray); + readonly id: number; + readonly name: (undefined | string); + readonly rank: (undefined | number) +}; + +/** 'JsonPopulateRecordsetJsonCast' query type */ +export type jsonPopulateRecordsetJsonCastQuery = { readonly params: jsonPopulateRecordsetJsonCastParams; readonly result: jsonPopulateRecordsetJsonCastResult }; + +export type jsonbPopulateRecord_booksInputType = { + readonly author_id?: number; + readonly categories?: categoryArray; + readonly id?: number; + readonly name?: string; + readonly rank?: number +}; + +/** 'JsonbPopulateRecord' parameters type */ +export type jsonbPopulateRecordParams = { readonly book: jsonbPopulateRecord_booksInputType }; + +/** 'JsonbPopulateRecord' return type */ +export type jsonbPopulateRecordResult = { + readonly author_id: (undefined | number); + readonly categories: (undefined | categoryArray); + readonly id: (undefined | number); + readonly name: (undefined | string); + readonly rank: (undefined | number) +}; + +/** 'JsonbPopulateRecord' query type */ +export type jsonbPopulateRecordQuery = { readonly params: jsonbPopulateRecordParams; readonly result: jsonbPopulateRecordResult }; + +export type jsonbPopulateRecordset_booksInputType = { + readonly author_id?: number; + readonly categories?: categoryArray; + readonly id?: number; + readonly name?: string; + readonly rank?: number +}; + +/** 'JsonbPopulateRecordset' parameters type */ +export type jsonbPopulateRecordsetParams = { readonly books: jsonbPopulateRecordset_booksInputType[] }; + +/** 'JsonbPopulateRecordset' return type */ +export type jsonbPopulateRecordsetResult = { + readonly author_id: (undefined | number); + readonly categories: (undefined | categoryArray); + readonly id: (undefined | number); + readonly name: (undefined | string); + readonly rank: (undefined | number) +}; + +/** 'JsonbPopulateRecordset' query type */ +export type jsonbPopulateRecordsetQuery = { readonly params: jsonbPopulateRecordsetParams; readonly result: jsonbPopulateRecordsetResult }; + /** Returns an array of all matched results. */ export const Json_many: (_1:PgTyped_Pg_Client_t, _2:jsonParams) => Promise = Json__sqlJS.Json.many as any; @@ -30,7 +161,144 @@ export const Json_expectOne: (_1:PgTyped_Pg_Client_t, _2:jsonParams, errorMessag /** Executes the query, but ignores whatever is returned by it. */ export const Json_execute: (_1:PgTyped_Pg_Client_t, _2:jsonParams) => Promise = Json__sqlJS.Json.execute as any; -export const json: (params:jsonParams, client:PgTyped_Pg_Client_t) => Promise = Json__sqlJS.json as any; +/** Returns an array of all matched results. */ +export const JsonExtract_many: (_1:PgTyped_Pg_Client_t, _2:jsonExtractParams) => Promise = Json__sqlJS.JsonExtract.many as any; + +/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ +export const JsonExtract_one: (_1:PgTyped_Pg_Client_t, _2:jsonExtractParams) => Promise<(undefined | jsonExtractResult)> = Json__sqlJS.JsonExtract.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 JsonExtract_expectOne: (_1:PgTyped_Pg_Client_t, _2:jsonExtractParams, errorMessage:(undefined | string)) => Promise = Json__sqlJS.JsonExtract.expectOne as any; + +/** Executes the query, but ignores whatever is returned by it. */ +export const JsonExtract_execute: (_1:PgTyped_Pg_Client_t, _2:jsonExtractParams) => Promise = Json__sqlJS.JsonExtract.execute as any; + +/** Returns an array of all matched results. */ +export const JsonUnnestCast_many: (_1:PgTyped_Pg_Client_t, _2:jsonUnnestCastParams) => Promise = Json__sqlJS.JsonUnnestCast.many as any; + +/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ +export const JsonUnnestCast_one: (_1:PgTyped_Pg_Client_t, _2:jsonUnnestCastParams) => Promise<(undefined | jsonUnnestCastResult)> = Json__sqlJS.JsonUnnestCast.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 JsonUnnestCast_expectOne: (_1:PgTyped_Pg_Client_t, _2:jsonUnnestCastParams, errorMessage:(undefined | string)) => Promise = Json__sqlJS.JsonUnnestCast.expectOne as any; + +/** Executes the query, but ignores whatever is returned by it. */ +export const JsonUnnestCast_execute: (_1:PgTyped_Pg_Client_t, _2:jsonUnnestCastParams) => Promise = Json__sqlJS.JsonUnnestCast.execute as any; + +/** Returns an array of all matched results. */ +export const JsonPopulateRecord_many: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordParams) => Promise = Json__sqlJS.JsonPopulateRecord.many as any; + +/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ +export const JsonPopulateRecord_one: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordParams) => Promise<(undefined | jsonPopulateRecordResult)> = Json__sqlJS.JsonPopulateRecord.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 JsonPopulateRecord_expectOne: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordParams, errorMessage:(undefined | string)) => Promise = Json__sqlJS.JsonPopulateRecord.expectOne as any; + +/** Executes the query, but ignores whatever is returned by it. */ +export const JsonPopulateRecord_execute: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordParams) => Promise = Json__sqlJS.JsonPopulateRecord.execute as any; + +/** Returns an array of all matched results. */ +export const JsonPopulateRecordset_many: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetParams) => Promise = Json__sqlJS.JsonPopulateRecordset.many as any; + +/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ +export const JsonPopulateRecordset_one: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetParams) => Promise<(undefined | jsonPopulateRecordsetResult)> = Json__sqlJS.JsonPopulateRecordset.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 JsonPopulateRecordset_expectOne: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetParams, errorMessage:(undefined | string)) => Promise = Json__sqlJS.JsonPopulateRecordset.expectOne as any; + +/** Executes the query, but ignores whatever is returned by it. */ +export const JsonPopulateRecordset_execute: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetParams) => Promise = Json__sqlJS.JsonPopulateRecordset.execute as any; + +/** Returns an array of all matched results. */ +export const JsonPopulateRecordsetJsonCast_many: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetJsonCastParams) => Promise = Json__sqlJS.JsonPopulateRecordsetJsonCast.many as any; + +/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ +export const JsonPopulateRecordsetJsonCast_one: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetJsonCastParams) => Promise<(undefined | jsonPopulateRecordsetJsonCastResult)> = Json__sqlJS.JsonPopulateRecordsetJsonCast.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 JsonPopulateRecordsetJsonCast_expectOne: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetJsonCastParams, errorMessage:(undefined | string)) => Promise = Json__sqlJS.JsonPopulateRecordsetJsonCast.expectOne as any; + +/** Executes the query, but ignores whatever is returned by it. */ +export const JsonPopulateRecordsetJsonCast_execute: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetJsonCastParams) => Promise = Json__sqlJS.JsonPopulateRecordsetJsonCast.execute as any; + +/** Returns an array of all matched results. */ +export const JsonbPopulateRecord_many: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordParams) => Promise = Json__sqlJS.JsonbPopulateRecord.many as any; + +/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ +export const JsonbPopulateRecord_one: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordParams) => Promise<(undefined | jsonbPopulateRecordResult)> = Json__sqlJS.JsonbPopulateRecord.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 JsonbPopulateRecord_expectOne: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordParams, errorMessage:(undefined | string)) => Promise = Json__sqlJS.JsonbPopulateRecord.expectOne as any; + +/** Executes the query, but ignores whatever is returned by it. */ +export const JsonbPopulateRecord_execute: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordParams) => Promise = Json__sqlJS.JsonbPopulateRecord.execute as any; + +/** Returns an array of all matched results. */ +export const JsonbPopulateRecordset_many: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordsetParams) => Promise = Json__sqlJS.JsonbPopulateRecordset.many as any; + +/** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ +export const JsonbPopulateRecordset_one: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordsetParams) => Promise<(undefined | jsonbPopulateRecordsetResult)> = Json__sqlJS.JsonbPopulateRecordset.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 JsonbPopulateRecordset_expectOne: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordsetParams, errorMessage:(undefined | string)) => Promise = Json__sqlJS.JsonbPopulateRecordset.expectOne as any; + +/** Executes the query, but ignores whatever is returned by it. */ +export const JsonbPopulateRecordset_execute: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordsetParams) => Promise = Json__sqlJS.JsonbPopulateRecordset.execute as any; + +export const JsonExtract: { + /** 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:jsonExtractParams, 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:jsonExtractParams) => Promise<(undefined | jsonExtractResult)>; + /** Returns an array of all matched results. */ + many: (_1:PgTyped_Pg_Client_t, _2:jsonExtractParams) => Promise; + /** Executes the query, but ignores whatever is returned by it. */ + execute: (_1:PgTyped_Pg_Client_t, _2:jsonExtractParams) => Promise +} = Json__sqlJS.JsonExtract as any; + +export const JsonbPopulateRecord: { + /** 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:jsonbPopulateRecordParams, 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:jsonbPopulateRecordParams) => Promise<(undefined | jsonbPopulateRecordResult)>; + /** Returns an array of all matched results. */ + many: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordParams) => Promise; + /** Executes the query, but ignores whatever is returned by it. */ + execute: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordParams) => Promise +} = Json__sqlJS.JsonbPopulateRecord as any; + +export const JsonPopulateRecord: { + /** 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:jsonPopulateRecordParams, 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:jsonPopulateRecordParams) => Promise<(undefined | jsonPopulateRecordResult)>; + /** Returns an array of all matched results. */ + many: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordParams) => Promise; + /** Executes the query, but ignores whatever is returned by it. */ + execute: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordParams) => Promise +} = Json__sqlJS.JsonPopulateRecord as any; + +export const JsonPopulateRecordset: { + /** 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:jsonPopulateRecordsetParams, 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:jsonPopulateRecordsetParams) => Promise<(undefined | jsonPopulateRecordsetResult)>; + /** Returns an array of all matched results. */ + many: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetParams) => Promise; + /** Executes the query, but ignores whatever is returned by it. */ + execute: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetParams) => Promise +} = Json__sqlJS.JsonPopulateRecordset as any; + +export const JsonbPopulateRecordset: { + /** 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:jsonbPopulateRecordsetParams, 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:jsonbPopulateRecordsetParams) => Promise<(undefined | jsonbPopulateRecordsetResult)>; + /** Returns an array of all matched results. */ + many: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordsetParams) => Promise; + /** Executes the query, but ignores whatever is returned by it. */ + execute: (_1:PgTyped_Pg_Client_t, _2:jsonbPopulateRecordsetParams) => Promise +} = Json__sqlJS.JsonbPopulateRecordset as any; export const Json: { /** Returns exactly 1 result. Raises `Exn.t` (with an optionally provided `errorMessage`) if more or less than exactly 1 result is returned. */ @@ -42,3 +310,25 @@ export const Json: { /** Executes the query, but ignores whatever is returned by it. */ execute: (_1:PgTyped_Pg_Client_t, _2:jsonParams) => Promise } = Json__sqlJS.Json as any; + +export const JsonUnnestCast: { + /** 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:jsonUnnestCastParams, 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:jsonUnnestCastParams) => Promise<(undefined | jsonUnnestCastResult)>; + /** Returns an array of all matched results. */ + many: (_1:PgTyped_Pg_Client_t, _2:jsonUnnestCastParams) => Promise; + /** Executes the query, but ignores whatever is returned by it. */ + execute: (_1:PgTyped_Pg_Client_t, _2:jsonUnnestCastParams) => Promise +} = Json__sqlJS.JsonUnnestCast as any; + +export const JsonPopulateRecordsetJsonCast: { + /** 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:jsonPopulateRecordsetJsonCastParams, 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:jsonPopulateRecordsetJsonCastParams) => Promise<(undefined | jsonPopulateRecordsetJsonCastResult)>; + /** Returns an array of all matched results. */ + many: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetJsonCastParams) => Promise; + /** Executes the query, but ignores whatever is returned by it. */ + execute: (_1:PgTyped_Pg_Client_t, _2:jsonPopulateRecordsetJsonCastParams) => Promise +} = Json__sqlJS.JsonPopulateRecordsetJsonCast as any; diff --git a/packages/example/src/books/Json__sql.res b/packages/example/src/books/Json__sql.res index b8b50b4e..9667f9e9 100644 --- a/packages/example/src/books/Json__sql.res +++ b/packages/example/src/books/Json__sql.res @@ -2,6 +2,16 @@ open PgTyped +@gentype +type category = [#"novel" | #"science-fiction" | #"thriller"] + +@gentype +type arrayJSON_t = array + +@gentype +type categoryArray = array + + /** 'Json' parameters type */ @gentype type jsonParams = unit @@ -74,8 +84,645 @@ module Json: { } } + + +/** 'JsonExtract' parameters type */ +@gentype +type jsonExtractParams = { + jsonData: JSON.t, +} + +/** 'JsonExtract' return type */ +@gentype +type jsonExtractResult = { + user_id: option, + user_name: option, +} + +/** 'JsonExtract' query type */ @gentype -@deprecated("Use 'Json.many' directly instead") -let json = (params, ~client) => Json.many(client, params) +type jsonExtractQuery = { + params: jsonExtractParams, + result: jsonExtractResult, +} + +%%private(let jsonExtractIR: IR.t = %raw(`{"queryName":"JsonExtract","inputParamTransforms":{"1":{"type":"stringify"}},"usedParamSet":{"jsonData":true},"params":[{"name":"jsonData","required":true,"transform":{"type":"scalar"},"locs":[{"a":96,"b":105}]}],"statement":"SELECT \n value->>'name' AS user_name,\n value->>'id' AS user_id\n FROM json_array_elements(:jsonData!::json) AS value"}`)) + +/** + Runnable query: + ```sql +SELECT + value->>'name' AS user_name, + value->>'id' AS user_id + FROM json_array_elements($1::json) AS value + ``` + + */ +@gentype +module JsonExtract: { + /** Returns an array of all matched results. */ + @gentype + let many: (PgTyped.Pg.Client.t, jsonExtractParams) => promise> + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + @gentype + let one: (PgTyped.Pg.Client.t, jsonExtractParams) => 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, + jsonExtractParams, + ~errorMessage: string=? + ) => promise + + /** Executes the query, but ignores whatever is returned by it. */ + @gentype + let execute: (PgTyped.Pg.Client.t, jsonExtractParams) => promise +} = { + @module("pgtyped-rescript-runtime") @new external jsonExtract: IR.t => PreparedStatement.t = "PreparedQuery"; + let query = jsonExtract(jsonExtractIR) + 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) + } +} + + + +/** 'JsonUnnestCast' parameters type */ +@gentype +type jsonUnnestCastParams = { + jsonData: arrayJSON_t, +} + +/** 'JsonUnnestCast' return type */ +@gentype +type jsonUnnestCastResult = { + json_arr: option, +} + +/** 'JsonUnnestCast' query type */ +@gentype +type jsonUnnestCastQuery = { + params: jsonUnnestCastParams, + result: jsonUnnestCastResult, +} + +%%private(let jsonUnnestCastIR: IR.t = %raw(`{"queryName":"JsonUnnestCast","inputParamTransforms":{"1":{"type":"stringify"}},"usedParamSet":{"jsonData":true},"params":[{"name":"jsonData","required":true,"transform":{"type":"scalar"},"locs":[{"a":14,"b":23}]}],"statement":"SELECT unnest(:jsonData!::json[]) AS json_arr"}`)) + +/** + Runnable query: + ```sql +SELECT unnest($1::json[]) AS json_arr + ``` + + */ +@gentype +module JsonUnnestCast: { + /** Returns an array of all matched results. */ + @gentype + let many: (PgTyped.Pg.Client.t, jsonUnnestCastParams) => promise> + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + @gentype + let one: (PgTyped.Pg.Client.t, jsonUnnestCastParams) => 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, + jsonUnnestCastParams, + ~errorMessage: string=? + ) => promise + + /** Executes the query, but ignores whatever is returned by it. */ + @gentype + let execute: (PgTyped.Pg.Client.t, jsonUnnestCastParams) => promise +} = { + @module("pgtyped-rescript-runtime") @new external jsonUnnestCast: IR.t => PreparedStatement.t = "PreparedQuery"; + let query = jsonUnnestCast(jsonUnnestCastIR) + 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 +type jsonPopulateRecord_booksInputType = { + author_id?: int, + categories?: categoryArray, + id?: int, + name?: string, + rank?: int, +} + + +/** 'JsonPopulateRecord' parameters type */ +@gentype +type jsonPopulateRecordParams = { + book: jsonPopulateRecord_booksInputType, +} + +/** 'JsonPopulateRecord' return type */ +@gentype +type jsonPopulateRecordResult = { + author_id: option, + categories: option, + id: option, + name: option, + rank: option, +} + +/** 'JsonPopulateRecord' query type */ +@gentype +type jsonPopulateRecordQuery = { + params: jsonPopulateRecordParams, + result: jsonPopulateRecordResult, +} + +%%private(let jsonPopulateRecordIR: IR.t = %raw(`{"queryName":"JsonPopulateRecord","inputParamTransforms":{"1":{"type":"stringify"}},"usedParamSet":{"book":true},"params":[{"name":"book","required":true,"transform":{"type":"scalar"},"locs":[{"a":57,"b":62}]}],"statement":"SELECT * FROM json_populate_record(\n null::books,\n :book!\n )"}`)) + +/** + Runnable query: + ```sql +SELECT * FROM json_populate_record( + null::books, + $1 + ) + ``` + + */ +@gentype +module JsonPopulateRecord: { + /** Returns an array of all matched results. */ + @gentype + let many: (PgTyped.Pg.Client.t, jsonPopulateRecordParams) => promise> + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + @gentype + let one: (PgTyped.Pg.Client.t, jsonPopulateRecordParams) => 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, + jsonPopulateRecordParams, + ~errorMessage: string=? + ) => promise + + /** Executes the query, but ignores whatever is returned by it. */ + @gentype + let execute: (PgTyped.Pg.Client.t, jsonPopulateRecordParams) => promise +} = { + @module("pgtyped-rescript-runtime") @new external jsonPopulateRecord: IR.t => PreparedStatement.t = "PreparedQuery"; + let query = jsonPopulateRecord(jsonPopulateRecordIR) + 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 +type jsonPopulateRecordset_booksInputType = { + author_id?: int, + categories?: categoryArray, + id?: int, + name?: string, + rank?: int, +} + + +/** 'JsonPopulateRecordset' parameters type */ +@gentype +type jsonPopulateRecordsetParams = { + books: array, +} + +/** 'JsonPopulateRecordset' return type */ +@gentype +type jsonPopulateRecordsetResult = { + author_id: option, + categories: option, + id: int, + name: option, + rank: option, +} + +/** 'JsonPopulateRecordset' query type */ +@gentype +type jsonPopulateRecordsetQuery = { + params: jsonPopulateRecordsetParams, + result: jsonPopulateRecordsetResult, +} + +%%private(let jsonPopulateRecordsetIR: IR.t = %raw(`{"queryName":"JsonPopulateRecordset","inputParamTransforms":{"1":{"type":"stringify"}},"usedParamSet":{"books":true},"params":[{"name":"books","required":true,"transform":{"type":"scalar"},"locs":[{"a":223,"b":229}]}],"statement":"insert into books (\n name, \n author_id, \n categories, \n rank\n )\n select\n event.name,\n event.author_id,\n event.categories,\n event.rank\n from json_populate_recordset(\n null::books,\n :books!\n ) as event\n on conflict (id) do update set \n name = excluded.name,\n author_id = excluded.author_id,\n categories = excluded.categories,\n rank = excluded.rank\n returning *"}`)) + +/** + Runnable query: + ```sql +insert into books ( + name, + author_id, + categories, + rank + ) + select + event.name, + event.author_id, + event.categories, + event.rank + from json_populate_recordset( + null::books, + $1 + ) as event + on conflict (id) do update set + name = excluded.name, + author_id = excluded.author_id, + categories = excluded.categories, + rank = excluded.rank + returning * + ``` + + */ +@gentype +module JsonPopulateRecordset: { + /** Returns an array of all matched results. */ + @gentype + let many: (PgTyped.Pg.Client.t, jsonPopulateRecordsetParams) => promise> + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + @gentype + let one: (PgTyped.Pg.Client.t, jsonPopulateRecordsetParams) => 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, + jsonPopulateRecordsetParams, + ~errorMessage: string=? + ) => promise + + /** Executes the query, but ignores whatever is returned by it. */ + @gentype + let execute: (PgTyped.Pg.Client.t, jsonPopulateRecordsetParams) => promise +} = { + @module("pgtyped-rescript-runtime") @new external jsonPopulateRecordset: IR.t => PreparedStatement.t = "PreparedQuery"; + let query = jsonPopulateRecordset(jsonPopulateRecordsetIR) + 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) + } +} + + + +/** 'JsonPopulateRecordsetJsonCast' parameters type */ +@gentype +type jsonPopulateRecordsetJsonCastParams = { + books: JSON.t, +} + +/** 'JsonPopulateRecordsetJsonCast' return type */ +@gentype +type jsonPopulateRecordsetJsonCastResult = { + author_id: option, + categories: option, + id: int, + name: option, + rank: option, +} + +/** 'JsonPopulateRecordsetJsonCast' query type */ +@gentype +type jsonPopulateRecordsetJsonCastQuery = { + params: jsonPopulateRecordsetJsonCastParams, + result: jsonPopulateRecordsetJsonCastResult, +} + +%%private(let jsonPopulateRecordsetJsonCastIR: IR.t = %raw(`{"queryName":"JsonPopulateRecordsetJsonCast","inputParamTransforms":{"1":{"type":"stringify"}},"usedParamSet":{"books":true},"params":[{"name":"books","required":true,"transform":{"type":"scalar"},"locs":[{"a":223,"b":229}]}],"statement":"insert into books (\n name, \n author_id, \n categories, \n rank\n )\n select\n event.name,\n event.author_id,\n event.categories,\n event.rank\n from json_populate_recordset(\n null::books,\n :books!::json\n ) as event\n on conflict (id) do update set \n name = excluded.name,\n author_id = excluded.author_id,\n categories = excluded.categories,\n rank = excluded.rank\n returning *"}`)) + +/** + Runnable query: + ```sql +insert into books ( + name, + author_id, + categories, + rank + ) + select + event.name, + event.author_id, + event.categories, + event.rank + from json_populate_recordset( + null::books, + $1::json + ) as event + on conflict (id) do update set + name = excluded.name, + author_id = excluded.author_id, + categories = excluded.categories, + rank = excluded.rank + returning * + ``` + + */ +@gentype +module JsonPopulateRecordsetJsonCast: { + /** Returns an array of all matched results. */ + @gentype + let many: (PgTyped.Pg.Client.t, jsonPopulateRecordsetJsonCastParams) => promise> + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + @gentype + let one: (PgTyped.Pg.Client.t, jsonPopulateRecordsetJsonCastParams) => 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, + jsonPopulateRecordsetJsonCastParams, + ~errorMessage: string=? + ) => promise + + /** Executes the query, but ignores whatever is returned by it. */ + @gentype + let execute: (PgTyped.Pg.Client.t, jsonPopulateRecordsetJsonCastParams) => promise +} = { + @module("pgtyped-rescript-runtime") @new external jsonPopulateRecordsetJsonCast: IR.t => PreparedStatement.t = "PreparedQuery"; + let query = jsonPopulateRecordsetJsonCast(jsonPopulateRecordsetJsonCastIR) + 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 +type jsonbPopulateRecord_booksInputType = { + author_id?: int, + categories?: categoryArray, + id?: int, + name?: string, + rank?: int, +} + + +/** 'JsonbPopulateRecord' parameters type */ +@gentype +type jsonbPopulateRecordParams = { + book: jsonbPopulateRecord_booksInputType, +} + +/** 'JsonbPopulateRecord' return type */ +@gentype +type jsonbPopulateRecordResult = { + author_id: option, + categories: option, + id: option, + name: option, + rank: option, +} + +/** 'JsonbPopulateRecord' query type */ +@gentype +type jsonbPopulateRecordQuery = { + params: jsonbPopulateRecordParams, + result: jsonbPopulateRecordResult, +} + +%%private(let jsonbPopulateRecordIR: IR.t = %raw(`{"queryName":"JsonbPopulateRecord","inputParamTransforms":{"1":{"type":"stringify"}},"usedParamSet":{"book":true},"params":[{"name":"book","required":true,"transform":{"type":"scalar"},"locs":[{"a":58,"b":63}]}],"statement":"SELECT * FROM jsonb_populate_record(\n null::books,\n :book!\n )"}`)) + +/** + Runnable query: + ```sql +SELECT * FROM jsonb_populate_record( + null::books, + $1 + ) + ``` + + */ +@gentype +module JsonbPopulateRecord: { + /** Returns an array of all matched results. */ + @gentype + let many: (PgTyped.Pg.Client.t, jsonbPopulateRecordParams) => promise> + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + @gentype + let one: (PgTyped.Pg.Client.t, jsonbPopulateRecordParams) => 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, + jsonbPopulateRecordParams, + ~errorMessage: string=? + ) => promise + + /** Executes the query, but ignores whatever is returned by it. */ + @gentype + let execute: (PgTyped.Pg.Client.t, jsonbPopulateRecordParams) => promise +} = { + @module("pgtyped-rescript-runtime") @new external jsonbPopulateRecord: IR.t => PreparedStatement.t = "PreparedQuery"; + let query = jsonbPopulateRecord(jsonbPopulateRecordIR) + 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 +type jsonbPopulateRecordset_booksInputType = { + author_id?: int, + categories?: categoryArray, + id?: int, + name?: string, + rank?: int, +} + + +/** 'JsonbPopulateRecordset' parameters type */ +@gentype +type jsonbPopulateRecordsetParams = { + books: array, +} + +/** 'JsonbPopulateRecordset' return type */ +@gentype +type jsonbPopulateRecordsetResult = { + author_id: option, + categories: option, + id: option, + name: option, + rank: option, +} + +/** 'JsonbPopulateRecordset' query type */ +@gentype +type jsonbPopulateRecordsetQuery = { + params: jsonbPopulateRecordsetParams, + result: jsonbPopulateRecordsetResult, +} + +%%private(let jsonbPopulateRecordsetIR: IR.t = %raw(`{"queryName":"JsonbPopulateRecordset","inputParamTransforms":{"1":{"type":"stringify"}},"usedParamSet":{"books":true},"params":[{"name":"books","required":true,"transform":{"type":"scalar"},"locs":[{"a":61,"b":67}]}],"statement":"SELECT * FROM jsonb_populate_recordset(\n null::books,\n :books!\n )"}`)) + +/** + Runnable query: + ```sql +SELECT * FROM jsonb_populate_recordset( + null::books, + $1 + ) + ``` + + */ +@gentype +module JsonbPopulateRecordset: { + /** Returns an array of all matched results. */ + @gentype + let many: (PgTyped.Pg.Client.t, jsonbPopulateRecordsetParams) => promise> + /** Returns exactly 1 result. Returns `None` if more or less than exactly 1 result is returned. */ + @gentype + let one: (PgTyped.Pg.Client.t, jsonbPopulateRecordsetParams) => 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, + jsonbPopulateRecordsetParams, + ~errorMessage: string=? + ) => promise + + /** Executes the query, but ignores whatever is returned by it. */ + @gentype + let execute: (PgTyped.Pg.Client.t, jsonbPopulateRecordsetParams) => promise +} = { + @module("pgtyped-rescript-runtime") @new external jsonbPopulateRecordset: IR.t => PreparedStatement.t = "PreparedQuery"; + let query = jsonbPopulateRecordset(jsonbPopulateRecordsetIR) + 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) + } +} diff --git a/packages/example/src/books/Keywords__sql.gen.tsx b/packages/example/src/books/Keywords__sql.gen.tsx index 1a38c21e..2ee8422d 100644 --- a/packages/example/src/books/Keywords__sql.gen.tsx +++ b/packages/example/src/books/Keywords__sql.gen.tsx @@ -47,8 +47,6 @@ export const Keywords_expectOne: (_1:PgTyped_Pg_Client_t, _2:keywordsParams, err /** 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; diff --git a/packages/example/src/books/Keywords__sql.res b/packages/example/src/books/Keywords__sql.res index e5727a31..f42258c9 100644 --- a/packages/example/src/books/Keywords__sql.res +++ b/packages/example/src/books/Keywords__sql.res @@ -2,6 +2,7 @@ open PgTyped + /** 'Keywords' parameters type */ @gentype type keywordsParams = unit @@ -91,8 +92,4 @@ module Keywords: { } } -@gentype -@deprecated("Use 'Keywords.many' directly instead") -let keywords = (params, ~client) => Keywords.many(client, params) - diff --git a/packages/example/src/books/Misc__sql.gen.tsx b/packages/example/src/books/Misc__sql.gen.tsx index d5ef3c8d..e137b4fb 100644 --- a/packages/example/src/books/Misc__sql.gen.tsx +++ b/packages/example/src/books/Misc__sql.gen.tsx @@ -151,8 +151,6 @@ export const Literals_expectOne: (_1:PgTyped_Pg_Client_t, _2:literalsParams, err /** Executes the query, but ignores whatever is returned by it. */ export const Literals_execute: (_1:PgTyped_Pg_Client_t, _2:literalsParams) => Promise = Misc__sqlJS.Literals.execute as any; -export const literals: (params:literalsParams, client:PgTyped_Pg_Client_t) => Promise = Misc__sqlJS.literals as any; - /** Returns an array of all matched results. */ export const MoreLiterals_many: (_1:PgTyped_Pg_Client_t, _2:moreLiteralsParams) => Promise = Misc__sqlJS.MoreLiterals.many as any; @@ -165,8 +163,6 @@ export const MoreLiterals_expectOne: (_1:PgTyped_Pg_Client_t, _2:moreLiteralsPar /** Executes the query, but ignores whatever is returned by it. */ export const MoreLiterals_execute: (_1:PgTyped_Pg_Client_t, _2:moreLiteralsParams) => Promise = Misc__sqlJS.MoreLiterals.execute as any; -export const moreLiterals: (params:moreLiteralsParams, client:PgTyped_Pg_Client_t) => Promise = Misc__sqlJS.moreLiterals as any; - /** Returns an array of all matched results. */ export const DuplicateAliasTest_many: (_1:PgTyped_Pg_Client_t, _2:duplicateAliasTestParams) => Promise = Misc__sqlJS.DuplicateAliasTest.many as any; @@ -179,8 +175,6 @@ export const DuplicateAliasTest_expectOne: (_1:PgTyped_Pg_Client_t, _2:duplicate /** Executes the query, but ignores whatever is returned by it. */ export const DuplicateAliasTest_execute: (_1:PgTyped_Pg_Client_t, _2:duplicateAliasTestParams) => Promise = Misc__sqlJS.DuplicateAliasTest.execute as any; -export const duplicateAliasTest: (params:duplicateAliasTestParams, client:PgTyped_Pg_Client_t) => Promise = Misc__sqlJS.duplicateAliasTest as any; - /** Returns an array of all matched results. */ export const UnionTest_many: (_1:PgTyped_Pg_Client_t, _2:unionTestParams) => Promise = Misc__sqlJS.UnionTest.many as any; @@ -193,8 +187,6 @@ export const UnionTest_expectOne: (_1:PgTyped_Pg_Client_t, _2:unionTestParams, e /** Executes the query, but ignores whatever is returned by it. */ export const UnionTest_execute: (_1:PgTyped_Pg_Client_t, _2:unionTestParams) => Promise = Misc__sqlJS.UnionTest.execute as any; -export const unionTest: (params:unionTestParams, client:PgTyped_Pg_Client_t) => Promise = Misc__sqlJS.unionTest as any; - /** Returns an array of all matched results. */ export const UnionTestWithString_many: (_1:PgTyped_Pg_Client_t, _2:unionTestWithStringParams) => Promise = Misc__sqlJS.UnionTestWithString.many as any; @@ -207,8 +199,6 @@ export const UnionTestWithString_expectOne: (_1:PgTyped_Pg_Client_t, _2:unionTes /** Executes the query, but ignores whatever is returned by it. */ export const UnionTestWithString_execute: (_1:PgTyped_Pg_Client_t, _2:unionTestWithStringParams) => Promise = Misc__sqlJS.UnionTestWithString.execute as any; -export const unionTestWithString: (params:unionTestWithStringParams, client:PgTyped_Pg_Client_t) => Promise = Misc__sqlJS.unionTestWithString as any; - /** Returns an array of all matched results. */ export const SingleLiterals_many: (_1:PgTyped_Pg_Client_t, _2:singleLiteralsParams) => Promise = Misc__sqlJS.SingleLiterals.many as any; @@ -221,8 +211,6 @@ export const SingleLiterals_expectOne: (_1:PgTyped_Pg_Client_t, _2:singleLiteral /** Executes the query, but ignores whatever is returned by it. */ export const SingleLiterals_execute: (_1:PgTyped_Pg_Client_t, _2:singleLiteralsParams) => Promise = Misc__sqlJS.SingleLiterals.execute as any; -export const singleLiterals: (params:singleLiteralsParams, client:PgTyped_Pg_Client_t) => Promise = Misc__sqlJS.singleLiterals as any; - /** Returns an array of all matched results. */ export const EdgeCases_many: (_1:PgTyped_Pg_Client_t, _2:edgeCasesParams) => Promise = Misc__sqlJS.EdgeCases.many as any; @@ -235,8 +223,6 @@ export const EdgeCases_expectOne: (_1:PgTyped_Pg_Client_t, _2:edgeCasesParams, e /** Executes the query, but ignores whatever is returned by it. */ export const EdgeCases_execute: (_1:PgTyped_Pg_Client_t, _2:edgeCasesParams) => Promise = Misc__sqlJS.EdgeCases.execute as any; -export const edgeCases: (params:edgeCasesParams, client:PgTyped_Pg_Client_t) => Promise = Misc__sqlJS.edgeCases as any; - /** Returns an array of all matched results. */ export const ContextTest_many: (_1:PgTyped_Pg_Client_t, _2:contextTestParams) => Promise = Misc__sqlJS.ContextTest.many as any; @@ -249,8 +235,6 @@ export const ContextTest_expectOne: (_1:PgTyped_Pg_Client_t, _2:contextTestParam /** Executes the query, but ignores whatever is returned by it. */ export const ContextTest_execute: (_1:PgTyped_Pg_Client_t, _2:contextTestParams) => Promise = Misc__sqlJS.ContextTest.execute as any; -export const contextTest: (params:contextTestParams, client:PgTyped_Pg_Client_t) => Promise = Misc__sqlJS.contextTest as any; - export const MoreLiterals: { /** 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:moreLiteralsParams, errorMessage:(undefined | string)) => Promise; diff --git a/packages/example/src/books/Misc__sql.res b/packages/example/src/books/Misc__sql.res index df1b8430..8464a2ad 100644 --- a/packages/example/src/books/Misc__sql.res +++ b/packages/example/src/books/Misc__sql.res @@ -2,6 +2,7 @@ open PgTyped + /** 'Literals' parameters type */ @gentype type literalsParams = unit @@ -79,9 +80,6 @@ module Literals: { } } -@gentype -@deprecated("Use 'Literals.many' directly instead") -let literals = (params, ~client) => Literals.many(client, params) /** 'MoreLiterals' parameters type */ @@ -175,9 +173,6 @@ module MoreLiterals: { } } -@gentype -@deprecated("Use 'MoreLiterals.many' directly instead") -let moreLiterals = (params, ~client) => MoreLiterals.many(client, params) /** 'DuplicateAliasTest' parameters type */ @@ -257,9 +252,6 @@ module DuplicateAliasTest: { } } -@gentype -@deprecated("Use 'DuplicateAliasTest.many' directly instead") -let duplicateAliasTest = (params, ~client) => DuplicateAliasTest.many(client, params) /** 'UnionTest' parameters type */ @@ -337,9 +329,6 @@ module UnionTest: { } } -@gentype -@deprecated("Use 'UnionTest.many' directly instead") -let unionTest = (params, ~client) => UnionTest.many(client, params) /** 'UnionTestWithString' parameters type */ @@ -419,9 +408,6 @@ module UnionTestWithString: { } } -@gentype -@deprecated("Use 'UnionTestWithString.many' directly instead") -let unionTestWithString = (params, ~client) => UnionTestWithString.many(client, params) /** 'SingleLiterals' parameters type */ @@ -501,9 +487,6 @@ module SingleLiterals: { } } -@gentype -@deprecated("Use 'SingleLiterals.many' directly instead") -let singleLiterals = (params, ~client) => SingleLiterals.many(client, params) /** 'EdgeCases' parameters type */ @@ -593,9 +576,6 @@ module EdgeCases: { } } -@gentype -@deprecated("Use 'EdgeCases.many' directly instead") -let edgeCases = (params, ~client) => EdgeCases.many(client, params) /** 'ContextTest' parameters type */ @@ -683,8 +663,4 @@ module ContextTest: { } } -@gentype -@deprecated("Use 'ContextTest.many' directly instead") -let contextTest = (params, ~client) => ContextTest.many(client, params) - diff --git a/packages/example/src/books/books__sql.gen.tsx b/packages/example/src/books/books__sql.gen.tsx index 76f85e62..05a6fee7 100644 --- a/packages/example/src/books/books__sql.gen.tsx +++ b/packages/example/src/books/books__sql.gen.tsx @@ -421,8 +421,6 @@ export const FindBookById_expectOne: (_1:PgTyped_Pg_Client_t, _2:findBookByIdPar /** Executes the query, but ignores whatever is returned by it. */ export const FindBookById_execute: (_1:PgTyped_Pg_Client_t, _2:findBookByIdParams) => Promise = books__sqlJS.FindBookById.execute as any; -export const findBookById: (params:findBookByIdParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.findBookById as any; - /** Returns an array of all matched results. */ export const FindBookByCategory_many: (_1:PgTyped_Pg_Client_t, _2:findBookByCategoryParams) => Promise = books__sqlJS.FindBookByCategory.many as any; @@ -435,8 +433,6 @@ export const FindBookByCategory_expectOne: (_1:PgTyped_Pg_Client_t, _2:findBookB /** Executes the query, but ignores whatever is returned by it. */ export const FindBookByCategory_execute: (_1:PgTyped_Pg_Client_t, _2:findBookByCategoryParams) => Promise = books__sqlJS.FindBookByCategory.execute as any; -export const findBookByCategory: (params:findBookByCategoryParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.findBookByCategory as any; - /** Returns an array of all matched results. */ export const FindBookNameOrRank_many: (_1:PgTyped_Pg_Client_t, _2:findBookNameOrRankParams) => Promise = books__sqlJS.FindBookNameOrRank.many as any; @@ -449,8 +445,6 @@ export const FindBookNameOrRank_expectOne: (_1:PgTyped_Pg_Client_t, _2:findBookN /** Executes the query, but ignores whatever is returned by it. */ export const FindBookNameOrRank_execute: (_1:PgTyped_Pg_Client_t, _2:findBookNameOrRankParams) => Promise = books__sqlJS.FindBookNameOrRank.execute as any; -export const findBookNameOrRank: (params:findBookNameOrRankParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.findBookNameOrRank as any; - /** Returns an array of all matched results. */ export const FindBookUnicode_many: (_1:PgTyped_Pg_Client_t, _2:findBookUnicodeParams) => Promise = books__sqlJS.FindBookUnicode.many as any; @@ -463,8 +457,6 @@ export const FindBookUnicode_expectOne: (_1:PgTyped_Pg_Client_t, _2:findBookUnic /** Executes the query, but ignores whatever is returned by it. */ export const FindBookUnicode_execute: (_1:PgTyped_Pg_Client_t, _2:findBookUnicodeParams) => Promise = books__sqlJS.FindBookUnicode.execute as any; -export const findBookUnicode: (params:findBookUnicodeParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.findBookUnicode as any; - /** Returns an array of all matched results. */ export const InsertBooks_many: (_1:PgTyped_Pg_Client_t, _2:insertBooksParams) => Promise = books__sqlJS.InsertBooks.many as any; @@ -477,8 +469,6 @@ export const InsertBooks_expectOne: (_1:PgTyped_Pg_Client_t, _2:insertBooksParam /** Executes the query, but ignores whatever is returned by it. */ export const InsertBooks_execute: (_1:PgTyped_Pg_Client_t, _2:insertBooksParams) => Promise = books__sqlJS.InsertBooks.execute as any; -export const insertBooks: (params:insertBooksParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.insertBooks as any; - /** Returns an array of all matched results. */ export const InsertBook_many: (_1:PgTyped_Pg_Client_t, _2:insertBookParams) => Promise = books__sqlJS.InsertBook.many as any; @@ -491,8 +481,6 @@ export const InsertBook_expectOne: (_1:PgTyped_Pg_Client_t, _2:insertBookParams, /** Executes the query, but ignores whatever is returned by it. */ export const InsertBook_execute: (_1:PgTyped_Pg_Client_t, _2:insertBookParams) => Promise = books__sqlJS.InsertBook.execute as any; -export const insertBook: (params:insertBookParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.insertBook as any; - /** Returns an array of all matched results. */ export const UpdateBooksCustom_many: (_1:PgTyped_Pg_Client_t, _2:updateBooksCustomParams) => Promise = books__sqlJS.UpdateBooksCustom.many as any; @@ -505,8 +493,6 @@ export const UpdateBooksCustom_expectOne: (_1:PgTyped_Pg_Client_t, _2:updateBook /** Executes the query, but ignores whatever is returned by it. */ export const UpdateBooksCustom_execute: (_1:PgTyped_Pg_Client_t, _2:updateBooksCustomParams) => Promise = books__sqlJS.UpdateBooksCustom.execute as any; -export const updateBooksCustom: (params:updateBooksCustomParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.updateBooksCustom as any; - /** Returns an array of all matched results. */ export const UpdateBooks_many: (_1:PgTyped_Pg_Client_t, _2:updateBooksParams) => Promise = books__sqlJS.UpdateBooks.many as any; @@ -519,8 +505,6 @@ export const UpdateBooks_expectOne: (_1:PgTyped_Pg_Client_t, _2:updateBooksParam /** Executes the query, but ignores whatever is returned by it. */ export const UpdateBooks_execute: (_1:PgTyped_Pg_Client_t, _2:updateBooksParams) => Promise = books__sqlJS.UpdateBooks.execute as any; -export const updateBooks: (params:updateBooksParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.updateBooks as any; - /** Returns an array of all matched results. */ export const UpdateBooksRankNotNull_many: (_1:PgTyped_Pg_Client_t, _2:updateBooksRankNotNullParams) => Promise = books__sqlJS.UpdateBooksRankNotNull.many as any; @@ -533,8 +517,6 @@ export const UpdateBooksRankNotNull_expectOne: (_1:PgTyped_Pg_Client_t, _2:updat /** Executes the query, but ignores whatever is returned by it. */ export const UpdateBooksRankNotNull_execute: (_1:PgTyped_Pg_Client_t, _2:updateBooksRankNotNullParams) => Promise = books__sqlJS.UpdateBooksRankNotNull.execute as any; -export const updateBooksRankNotNull: (params:updateBooksRankNotNullParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.updateBooksRankNotNull as any; - /** Returns an array of all matched results. */ export const GetBooksByAuthorName_many: (_1:PgTyped_Pg_Client_t, _2:getBooksByAuthorNameParams) => Promise = books__sqlJS.GetBooksByAuthorName.many as any; @@ -547,8 +529,6 @@ export const GetBooksByAuthorName_expectOne: (_1:PgTyped_Pg_Client_t, _2:getBook /** Executes the query, but ignores whatever is returned by it. */ export const GetBooksByAuthorName_execute: (_1:PgTyped_Pg_Client_t, _2:getBooksByAuthorNameParams) => Promise = books__sqlJS.GetBooksByAuthorName.execute as any; -export const getBooksByAuthorName: (params:getBooksByAuthorNameParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.getBooksByAuthorName as any; - /** Returns an array of all matched results. */ export const AggregateEmailsAndTest_many: (_1:PgTyped_Pg_Client_t, _2:aggregateEmailsAndTestParams) => Promise = books__sqlJS.AggregateEmailsAndTest.many as any; @@ -561,8 +541,6 @@ export const AggregateEmailsAndTest_expectOne: (_1:PgTyped_Pg_Client_t, _2:aggre /** Executes the query, but ignores whatever is returned by it. */ export const AggregateEmailsAndTest_execute: (_1:PgTyped_Pg_Client_t, _2:aggregateEmailsAndTestParams) => Promise = books__sqlJS.AggregateEmailsAndTest.execute as any; -export const aggregateEmailsAndTest: (params:aggregateEmailsAndTestParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.aggregateEmailsAndTest as any; - /** Returns an array of all matched results. */ export const GetBooks_many: (_1:PgTyped_Pg_Client_t, _2:getBooksParams) => Promise = books__sqlJS.GetBooks.many as any; @@ -575,8 +553,6 @@ export const GetBooks_expectOne: (_1:PgTyped_Pg_Client_t, _2:getBooksParams, err /** Executes the query, but ignores whatever is returned by it. */ export const GetBooks_execute: (_1:PgTyped_Pg_Client_t, _2:getBooksParams) => Promise = books__sqlJS.GetBooks.execute as any; -export const getBooks: (params:getBooksParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.getBooks as any; - /** Returns an array of all matched results. */ export const CountBooks_many: (_1:PgTyped_Pg_Client_t, _2:countBooksParams) => Promise = books__sqlJS.CountBooks.many as any; @@ -589,8 +565,6 @@ export const CountBooks_expectOne: (_1:PgTyped_Pg_Client_t, _2:countBooksParams, /** Executes the query, but ignores whatever is returned by it. */ export const CountBooks_execute: (_1:PgTyped_Pg_Client_t, _2:countBooksParams) => Promise = books__sqlJS.CountBooks.execute as any; -export const countBooks: (params:countBooksParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.countBooks as any; - /** Returns an array of all matched results. */ export const GetBookCountries_many: (_1:PgTyped_Pg_Client_t, _2:getBookCountriesParams) => Promise = books__sqlJS.GetBookCountries.many as any; @@ -603,8 +577,6 @@ export const GetBookCountries_expectOne: (_1:PgTyped_Pg_Client_t, _2:getBookCoun /** Executes the query, but ignores whatever is returned by it. */ export const GetBookCountries_execute: (_1:PgTyped_Pg_Client_t, _2:getBookCountriesParams) => Promise = books__sqlJS.GetBookCountries.execute as any; -export const getBookCountries: (params:getBookCountriesParams, client:PgTyped_Pg_Client_t) => Promise = books__sqlJS.getBookCountries as any; - export const FindBookNameOrRank: { /** 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:findBookNameOrRankParams, errorMessage:(undefined | string)) => Promise; diff --git a/packages/example/src/books/books__sql.res b/packages/example/src/books/books__sql.res index d6a80ed9..9405bc01 100644 --- a/packages/example/src/books/books__sql.res +++ b/packages/example/src/books/books__sql.res @@ -17,6 +17,7 @@ type intArray = array @gentype type stringArray = array + /** 'FindBookById' parameters type */ @gentype type findBookByIdParams = { @@ -95,9 +96,6 @@ module FindBookById: { } } -@gentype -@deprecated("Use 'FindBookById.many' directly instead") -let findBookById = (params, ~client) => FindBookById.many(client, params) /** 'FindBookByCategory' parameters type */ @@ -178,9 +176,6 @@ module FindBookByCategory: { } } -@gentype -@deprecated("Use 'FindBookByCategory.many' directly instead") -let findBookByCategory = (params, ~client) => FindBookByCategory.many(client, params) /** 'FindBookNameOrRank' parameters type */ @@ -261,9 +256,6 @@ module FindBookNameOrRank: { } } -@gentype -@deprecated("Use 'FindBookNameOrRank.many' directly instead") -let findBookNameOrRank = (params, ~client) => FindBookNameOrRank.many(client, params) /** 'FindBookUnicode' parameters type */ @@ -342,10 +334,6 @@ module FindBookUnicode: { } } -@gentype -@deprecated("Use 'FindBookUnicode.many' directly instead") -let findBookUnicode = (params, ~client) => FindBookUnicode.many(client, params) - @gentype type insertBooksParams_books = { @@ -429,9 +417,6 @@ module InsertBooks: { } } -@gentype -@deprecated("Use 'InsertBooks.many' directly instead") -let insertBooks = (params, ~client) => InsertBooks.many(client, params) /** 'InsertBook' parameters type */ @@ -512,9 +497,6 @@ module InsertBook: { } } -@gentype -@deprecated("Use 'InsertBook.many' directly instead") -let insertBook = (params, ~client) => InsertBook.many(client, params) /** 'UpdateBooksCustom' parameters type */ @@ -598,9 +580,6 @@ module UpdateBooksCustom: { } } -@gentype -@deprecated("Use 'UpdateBooksCustom.many' directly instead") -let updateBooksCustom = (params, ~client) => UpdateBooksCustom.many(client, params) /** 'UpdateBooks' parameters type */ @@ -682,9 +661,6 @@ module UpdateBooks: { } } -@gentype -@deprecated("Use 'UpdateBooks.many' directly instead") -let updateBooks = (params, ~client) => UpdateBooks.many(client, params) /** 'UpdateBooksRankNotNull' parameters type */ @@ -765,9 +741,6 @@ module UpdateBooksRankNotNull: { } } -@gentype -@deprecated("Use 'UpdateBooksRankNotNull.many' directly instead") -let updateBooksRankNotNull = (params, ~client) => UpdateBooksRankNotNull.many(client, params) /** 'GetBooksByAuthorName' parameters type */ @@ -850,9 +823,6 @@ module GetBooksByAuthorName: { } } -@gentype -@deprecated("Use 'GetBooksByAuthorName.many' directly instead") -let getBooksByAuthorName = (params, ~client) => GetBooksByAuthorName.many(client, params) /** 'AggregateEmailsAndTest' parameters type */ @@ -930,9 +900,6 @@ module AggregateEmailsAndTest: { } } -@gentype -@deprecated("Use 'AggregateEmailsAndTest.many' directly instead") -let aggregateEmailsAndTest = (params, ~client) => AggregateEmailsAndTest.many(client, params) /** 'GetBooks' parameters type */ @@ -1008,9 +975,6 @@ module GetBooks: { } } -@gentype -@deprecated("Use 'GetBooks.many' directly instead") -let getBooks = (params, ~client) => GetBooks.many(client, params) /** 'CountBooks' parameters type */ @@ -1085,9 +1049,6 @@ module CountBooks: { } } -@gentype -@deprecated("Use 'CountBooks.many' directly instead") -let countBooks = (params, ~client) => CountBooks.many(client, params) /** 'GetBookCountries' parameters type */ @@ -1163,8 +1124,4 @@ module GetBookCountries: { } } -@gentype -@deprecated("Use 'GetBookCountries.many' directly instead") -let getBookCountries = (params, ~client) => GetBookCountries.many(client, params) - diff --git a/packages/example/src/comments/comments__sql.gen.tsx b/packages/example/src/comments/comments__sql.gen.tsx index 4f7ad44a..65b3f506 100644 --- a/packages/example/src/comments/comments__sql.gen.tsx +++ b/packages/example/src/comments/comments__sql.gen.tsx @@ -72,8 +72,6 @@ export const GetAllComments_expectOne: (_1:PgTyped_Pg_Client_t, _2:getAllComment /** Executes the query, but ignores whatever is returned by it. */ export const GetAllComments_execute: (_1:PgTyped_Pg_Client_t, _2:getAllCommentsParams) => Promise = comments__sqlJS.GetAllComments.execute as any; -export const getAllComments: (params:getAllCommentsParams, client:PgTyped_Pg_Client_t) => Promise = comments__sqlJS.getAllComments as any; - /** Returns an array of all matched results. */ export const GetAllCommentsByIds_many: (_1:PgTyped_Pg_Client_t, _2:getAllCommentsByIdsParams) => Promise = comments__sqlJS.GetAllCommentsByIds.many as any; @@ -86,8 +84,6 @@ export const GetAllCommentsByIds_expectOne: (_1:PgTyped_Pg_Client_t, _2:getAllCo /** Executes the query, but ignores whatever is returned by it. */ export const GetAllCommentsByIds_execute: (_1:PgTyped_Pg_Client_t, _2:getAllCommentsByIdsParams) => Promise = comments__sqlJS.GetAllCommentsByIds.execute as any; -export const getAllCommentsByIds: (params:getAllCommentsByIdsParams, client:PgTyped_Pg_Client_t) => Promise = comments__sqlJS.getAllCommentsByIds as any; - /** Returns an array of all matched results. */ export const InsertComment_many: (_1:PgTyped_Pg_Client_t, _2:insertCommentParams) => Promise = comments__sqlJS.InsertComment.many as any; @@ -100,8 +96,6 @@ export const InsertComment_expectOne: (_1:PgTyped_Pg_Client_t, _2:insertCommentP /** Executes the query, but ignores whatever is returned by it. */ export const InsertComment_execute: (_1:PgTyped_Pg_Client_t, _2:insertCommentParams) => Promise = comments__sqlJS.InsertComment.execute as any; -export const insertComment: (params:insertCommentParams, client:PgTyped_Pg_Client_t) => Promise = comments__sqlJS.insertComment as any; - /** Returns an array of all matched results. */ export const SelectExistsTest_many: (_1:PgTyped_Pg_Client_t, _2:selectExistsTestParams) => Promise = comments__sqlJS.SelectExistsTest.many as any; @@ -114,8 +108,6 @@ export const SelectExistsTest_expectOne: (_1:PgTyped_Pg_Client_t, _2:selectExist /** Executes the query, but ignores whatever is returned by it. */ export const SelectExistsTest_execute: (_1:PgTyped_Pg_Client_t, _2:selectExistsTestParams) => Promise = comments__sqlJS.SelectExistsTest.execute as any; -export const selectExistsTest: (params:selectExistsTestParams, client:PgTyped_Pg_Client_t) => Promise = comments__sqlJS.selectExistsTest as any; - export const SelectExistsTest: { /** 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:selectExistsTestParams, errorMessage:(undefined | string)) => Promise; diff --git a/packages/example/src/comments/comments__sql.res b/packages/example/src/comments/comments__sql.res index 0fc5ddfd..69f332d3 100644 --- a/packages/example/src/comments/comments__sql.res +++ b/packages/example/src/comments/comments__sql.res @@ -2,6 +2,7 @@ open PgTyped + /** 'GetAllComments' parameters type */ @gentype type getAllCommentsParams = { @@ -79,9 +80,6 @@ module GetAllComments: { } } -@gentype -@deprecated("Use 'GetAllComments.many' directly instead") -let getAllComments = (params, ~client) => GetAllComments.many(client, params) /** 'GetAllCommentsByIds' parameters type */ @@ -161,10 +159,6 @@ module GetAllCommentsByIds: { } } -@gentype -@deprecated("Use 'GetAllCommentsByIds.many' directly instead") -let getAllCommentsByIds = (params, ~client) => GetAllCommentsByIds.many(client, params) - @gentype type insertCommentParams_comments = { @@ -250,9 +244,6 @@ module InsertComment: { } } -@gentype -@deprecated("Use 'InsertComment.many' directly instead") -let insertComment = (params, ~client) => InsertComment.many(client, params) /** 'SelectExistsTest' parameters type */ @@ -327,8 +318,4 @@ module SelectExistsTest: { } } -@gentype -@deprecated("Use 'SelectExistsTest.many' directly instead") -let selectExistsTest = (params, ~client) => SelectExistsTest.many(client, params) - diff --git a/packages/example/src/notifications/notifications__sql.gen.tsx b/packages/example/src/notifications/notifications__sql.gen.tsx index 26c1d130..4443705e 100644 --- a/packages/example/src/notifications/notifications__sql.gen.tsx +++ b/packages/example/src/notifications/notifications__sql.gen.tsx @@ -68,8 +68,6 @@ export const SendNotifications_expectOne: (_1:PgTyped_Pg_Client_t, _2:sendNotifi /** Executes the query, but ignores whatever is returned by it. */ export const SendNotifications_execute: (_1:PgTyped_Pg_Client_t, _2:sendNotificationsParams) => Promise = notifications__sqlJS.SendNotifications.execute as any; -export const sendNotifications: (params:sendNotificationsParams, client:PgTyped_Pg_Client_t) => Promise = notifications__sqlJS.sendNotifications as any; - /** Returns an array of all matched results. */ export const GetNotifications_many: (_1:PgTyped_Pg_Client_t, _2:getNotificationsParams) => Promise = notifications__sqlJS.GetNotifications.many as any; @@ -82,8 +80,6 @@ export const GetNotifications_expectOne: (_1:PgTyped_Pg_Client_t, _2:getNotifica /** Executes the query, but ignores whatever is returned by it. */ export const GetNotifications_execute: (_1:PgTyped_Pg_Client_t, _2:getNotificationsParams) => Promise = notifications__sqlJS.GetNotifications.execute as any; -export const getNotifications: (params:getNotificationsParams, client:PgTyped_Pg_Client_t) => Promise = notifications__sqlJS.getNotifications as any; - /** Returns an array of all matched results. */ export const ThresholdFrogs_many: (_1:PgTyped_Pg_Client_t, _2:thresholdFrogsParams) => Promise = notifications__sqlJS.ThresholdFrogs.many as any; @@ -96,8 +92,6 @@ export const ThresholdFrogs_expectOne: (_1:PgTyped_Pg_Client_t, _2:thresholdFrog /** Executes the query, but ignores whatever is returned by it. */ export const ThresholdFrogs_execute: (_1:PgTyped_Pg_Client_t, _2:thresholdFrogsParams) => Promise = notifications__sqlJS.ThresholdFrogs.execute as any; -export const thresholdFrogs: (params:thresholdFrogsParams, client:PgTyped_Pg_Client_t) => Promise = notifications__sqlJS.thresholdFrogs as any; - export const ThresholdFrogs: { /** 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:thresholdFrogsParams, errorMessage:(undefined | string)) => Promise; diff --git a/packages/example/src/notifications/notifications__sql.res b/packages/example/src/notifications/notifications__sql.res index 074cac55..f65d57ae 100644 --- a/packages/example/src/notifications/notifications__sql.res +++ b/packages/example/src/notifications/notifications__sql.res @@ -86,9 +86,6 @@ module SendNotifications: { } } -@gentype -@deprecated("Use 'SendNotifications.many' directly instead") -let sendNotifications = (params, ~client) => SendNotifications.many(client, params) /** 'GetNotifications' parameters type */ @@ -173,9 +170,6 @@ module GetNotifications: { } } -@gentype -@deprecated("Use 'GetNotifications.many' directly instead") -let getNotifications = (params, ~client) => GetNotifications.many(client, params) /** 'ThresholdFrogs' parameters type */ @@ -257,8 +251,4 @@ module ThresholdFrogs: { } } -@gentype -@deprecated("Use 'ThresholdFrogs.many' directly instead") -let thresholdFrogs = (params, ~client) => ThresholdFrogs.many(client, params) - diff --git a/packages/example/src/rescript.test.res b/packages/example/src/rescript.test.res index 22f5237e..69c4ba60 100644 --- a/packages/example/src/rescript.test.res +++ b/packages/example/src/rescript.test.res @@ -262,3 +262,54 @@ testAsync("`expectOne` works in fail case", async () => { expect(result)->Expect.toBe(true) }) + +testAsync("insert query with json_populate_recordset", async () => { + let (insertedBookId1, insertedBookId2) = switch await getClient()->BookService.insertBooks( + ~books=[ + { + author_id: 1, + name: "A Brief History of Time: From the Big Bang to Black Holes", + rank: 1, + categories: [#novel, #"science-fiction"], + }, + { + author_id: 1, + name: "A Brief History of Time: From the Big Bang to Black Holes 2", + rank: 2, + categories: [#"science-fiction"], + }, + ], + ) { + | [{id: id1}, {id: id2}] => (id1, id2) + | _ => panic("Unexpected result inserting books") + } + + switch await getClient()->Books.FindBookById.one({id: insertedBookId1}) { + | Some(insertedBook) => + expect(insertedBook.name)->Expect.toEqual( + "A Brief History of Time: From the Big Bang to Black Holes", + ) + | None => panic("Unexpected result fetching newly inserted book") + } + + switch await getClient()->Books.FindBookById.one({id: insertedBookId2}) { + | Some(insertedBook) => + expect(insertedBook.name)->Expect.toEqual( + "A Brief History of Time: From the Big Bang to Black Holes 2", + ) + | None => panic("Unexpected result fetching newly inserted book") + } +}) + +testAsync("json array inputs work", async () => { + let result = await getClient()->Json.JsonExtract.one({ + jsonData: JSON.Array([ + JSON.Object(Dict.fromArray([("id", JSON.String("1")), ("name", JSON.String("John"))])), + ]), + }) + + expect(result)->Expect.toEqual({ + "user_id": "1", + "user_name": "John", + }) +}) diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts index af27a43f..e324cef6 100644 --- a/packages/parser/src/index.ts +++ b/packages/parser/src/index.ts @@ -14,4 +14,6 @@ export { queryASTToIR, assert, TransformType, + InputParamTransforms, + InputParamTransform, } from './loader/sql/index.js'; diff --git a/packages/parser/src/loader/sql/index.ts b/packages/parser/src/loader/sql/index.ts index ca2c6d02..a511727a 100644 --- a/packages/parser/src/loader/sql/index.ts +++ b/packages/parser/src/loader/sql/index.ts @@ -71,6 +71,14 @@ export interface QueryAST { usedParamSet: { [paramName: string]: true }; } +export type InputParamTransformStringify = { + type: 'stringify'; +}; + +export type InputParamTransform = InputParamTransformStringify; + +export type InputParamTransforms = Record; + export interface ParamIR { name: string; transform: ParamTransform; @@ -85,6 +93,7 @@ export interface QueryIR { statement: string; usedParamSet: QueryAST['usedParamSet']; queryName: string; + inputParamTransforms: InputParamTransforms | undefined; } interface ParseTree { @@ -292,11 +301,15 @@ function parseText(text: string): SQLParseResult { }; } -export function queryASTToIR(query: SQLQueryAST): SQLQueryIR { +export function queryASTToIR( + query: SQLQueryAST, + inputParamTransforms: InputParamTransforms | null, +): SQLQueryIR { const { a: statementStart } = query.statement.loc; return { queryName: query.name, + inputParamTransforms: inputParamTransforms ?? undefined, usedParamSet: query.usedParamSet, params: query.params.map((param) => ({ name: param.name, diff --git a/packages/query/src/actions.ts b/packages/query/src/actions.ts index d9eb0c36..5ae68ae5 100644 --- a/packages/query/src/actions.ts +++ b/packages/query/src/actions.ts @@ -175,6 +175,23 @@ export interface IQueryTypes { nullable?: boolean; comment?: string; checkValues?: ConstraintValue[]; + defaultValue?: string; + }>; + inputTypes?: Record; +} + +export interface IInputTypes { + tableName: string; + assignedIndex: number; + multi: boolean; + stringify: boolean; + fields: Array<{ + columnName: string; + type: MappableType; + optional?: boolean; + comment?: string; + checkValues?: ConstraintValue[]; + defaultValue?: string; }>; } @@ -517,6 +534,12 @@ interface ColumnComment { comment: string; } +interface ColumnDefault { + tableOID: number; + columnAttrNumber: number; + defaultValue: string | null; +} + async function getComments( fields: TypeField[], queue: AsyncQueue, @@ -545,6 +568,38 @@ async function getComments( })); } +async function getDefaults( + fields: TypeField[], + queue: AsyncQueue, +): Promise { + const columnFields = fields.filter((f) => f.columnAttrNumber > 0); + if (columnFields.length === 0) { + return []; + } + + const tableOids = Array.from( + new Set(columnFields.map((f) => f.tableOID)), + ).join(','); + + const defaultRows = await runQuery( + `SELECT + attrelid, attnum, atthasdef, + CASE WHEN atthasdef THEN pg_get_expr(adbin, attrelid) ELSE NULL END as default_value + FROM pg_attribute a + LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum + WHERE a.attrelid IN (${tableOids}) + AND a.attnum > 0 + AND NOT a.attisdropped;`, + queue, + ); + + return defaultRows.map((row) => ({ + tableOID: Number(row[0]), + columnAttrNumber: Number(row[1]), + defaultValue: row[3], + })); +} + export function getAliasedLiterals( ast: Statement[], ): Map { @@ -668,6 +723,54 @@ export function getAliasedLiterals( return map; } +async function extraParameterInfo(query: Statement[]) { + const paramsInfo = new Map< + number, + { + recordName: string; + assignedIndex: number; + multi: boolean; + stringify: boolean; + } + >(); + + const visitor = astVisitor((v) => ({ + call: async (c) => { + if ( + c.function.name === 'json_populate_recordset' || + c.function.name === 'jsonb_populate_recordset' || + c.function.name === 'json_populate_record' || + c.function.name === 'jsonb_populate_record' + ) { + const [arg1, arg2] = c.args; + if ( + arg1.type === 'cast' && + arg1.operand.type === 'null' && + 'name' in arg1.to && + arg2.type === 'parameter' && + 'name' in arg2 + ) { + const assignedIndex = parseInt(arg2.name.slice(1), 10); + const name = arg1.to.name; + paramsInfo.set(assignedIndex, { + recordName: name, + assignedIndex, + multi: + c.function.name === 'json_populate_recordset' || + c.function.name === 'jsonb_populate_recordset', + stringify: true, + }); + } + } + + v.super().call(c); + }, + })); + + visitor.statement(query[0]); + return paramsInfo; +} + export async function getTypes( queryData: InterpolatedQuery, queue: AsyncQueue, @@ -688,6 +791,7 @@ export async function getTypes( const typeMap = reduceTypeRows(typeRows); const parsedQuery = parse(queryData.query); const aliasedLiterals = getAliasedLiterals(parsedQuery); + const paramsInfo = await extraParameterInfo(parsedQuery); const attrMatcher = ({ tableOID, @@ -754,12 +858,165 @@ export async function getTypes( } }); + const inputTypes: Record = {}; + + if (paramsInfo.size > 0) { + for (const [_, { recordName, assignedIndex, multi }] of paramsInfo) { + const paramTypeInfo = await getInputType( + recordName, + assignedIndex, + queue, + ); + if ('errorCode' in paramTypeInfo) { + // Ignore errors for now + } else { + inputTypes[assignedIndex] = { ...paramTypeInfo, multi }; + } + } + } + + const processedMapping = queryData.mapping.map((param) => { + if ( + 'assignedIndex' in param && + !Array.isArray(param.assignedIndex) && + paramsInfo.has(param.assignedIndex) + ) { + const paramInfo = paramsInfo.get(param.assignedIndex)!; + + return { + type: 'inputTypeReference' as const, + tableName: paramInfo.recordName, + assignedIndex: param.assignedIndex, + name: param.name, + }; + } + return param; + }); + const paramMetadata = { params: params.map(({ oid }) => typeMap[oid]), - mapping: queryData.mapping, + mapping: processedMapping, }; - return { paramMetadata, returnTypes }; + return { paramMetadata, returnTypes, inputTypes }; +} + +export async function getInputType( + tableName: string, + assignedIndex: number, + queue: AsyncQueue, +): Promise { + try { + // First, get the table OID + const tableOidRows = await runQuery( + `SELECT oid FROM pg_class WHERE relname = '${tableName}' AND relkind = 'r';`, + queue, + ); + + if (tableOidRows.length === 0) { + return { + errorCode: 'TABLE_NOT_FOUND', + message: `Table '${tableName}' not found`, + }; + } + + const tableOID = Number(tableOidRows[0][0]); + + // Get all columns for the table + const columnRows = await runQuery( + `SELECT + attrelid as table_oid, + attnum as column_attr_number, + atttypid as type_oid, + attname as name, + attnotnull, + atthasdef + FROM pg_attribute + WHERE attrelid = ${tableOID} + AND attnum > 0 + AND NOT attisdropped + ORDER BY attnum;`, + queue, + ); + + if (columnRows.length === 0) { + return { + errorCode: 'NO_COLUMNS_FOUND', + message: `No columns found for table '${tableName}'`, + }; + } + + // Create TypeField-like objects for compatibility with existing functions + const fields = columnRows.map((row) => ({ + name: row[3], // attname + tableOID: Number(row[0]), // attrelid + columnAttrNumber: Number(row[1]), // attnum + typeOID: Number(row[2]), // atttypid + typeSize: 0, // not needed for input types + typeModifier: 0, // not needed for input types + formatCode: 0, // not needed for input types + })); + + // Get type information + const returnTypesOIDs = fields.map((f) => f.typeOID); + const typeRows = await runTypesCatalogQuery(returnTypesOIDs, queue); + const commentRows = await getComments(fields, queue); + const checkRows = await getCheckConstraints(fields, queue); + const defaultRows = await getDefaults(fields, queue); + const typeMap = reduceTypeRows(typeRows); + + const getAttid = (col: Pick) => + `${col.tableOID}:${col.columnAttrNumber}`; + + // Create maps for lookups + const commentMap: { [attid: string]: string | undefined } = {}; + for (const c of commentRows) { + commentMap[`${c.tableOID}:${c.columnAttrNumber}`] = c.comment; + } + const checkMap: { [attid: string]: ConstraintValue[] } = {}; + for (const chk of checkRows) { + checkMap[`${chk.tableOID}:${chk.columnAttrNumber}`] = chk.values; + } + const defaultMap: { [attid: string]: ColumnDefault } = {}; + for (const def of defaultRows) { + defaultMap[`${def.tableOID}:${def.columnAttrNumber}`] = def; + } + + // Build input types - key difference: optionality based on NOT NULL + defaults + const inputTypes = fields.map((f, index) => { + const hasDefault = !!defaultMap[getAttid(f)]?.defaultValue; + const isNotNull = columnRows[index][4] === 't'; // attnotnull + + return { + columnName: f.name, + type: typeMap[f.typeOID], + // Column is optional if it has a default OR allows NULL + optional: hasDefault || !isNotNull, + ...(commentMap[getAttid(f)] + ? { comment: commentMap[getAttid(f)] } + : {}), + ...(checkMap[getAttid(f)] + ? { checkValues: checkMap[getAttid(f)] } + : {}), + ...(defaultMap[getAttid(f)]?.defaultValue + ? { defaultValue: defaultMap[getAttid(f)].defaultValue! } + : {}), + }; + }); + + return { + tableName, + assignedIndex, + multi: false, + fields: inputTypes, + stringify: true, + }; + } catch (error) { + return { + errorCode: 'QUERY_ERROR', + message: `Error querying table '${tableName}': ${(error as any).message}`, + }; + } } function toRescriptName(name: string): string { diff --git a/packages/runtime/src/preprocessor-sql.test.ts b/packages/runtime/src/preprocessor-sql.test.ts index 676293cd..97bba382 100644 --- a/packages/runtime/src/preprocessor-sql.test.ts +++ b/packages/runtime/src/preprocessor-sql.test.ts @@ -21,7 +21,7 @@ test('(SQL) no params', () => { name: 'selectSomeUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); @@ -55,7 +55,7 @@ test('(SQL) two scalar params, one forced as non-null', () => { name: 'UpdateBooksRankNotNull', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); expect(interpolationResult).toEqual(expectedInterpolationResult); @@ -99,7 +99,7 @@ test('(SQL) two scalar params', () => { name: 'selectSomeUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); @@ -138,7 +138,7 @@ test('(SQL) one param used twice', () => { name: 'selectUsersAndParents', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); @@ -180,7 +180,7 @@ test('(SQL) array param', () => { name: 'selectSomeUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); @@ -222,7 +222,7 @@ test('(SQL) array param used twice', () => { name: 'selectSomeUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); @@ -271,7 +271,7 @@ test('(SQL) array and scalar param', () => { name: 'selectSomeUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); @@ -325,7 +325,7 @@ test('(SQL) pick param', () => { name: 'insertUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); expect(interpolationResult).toEqual(expectedInterpolationResult); @@ -379,7 +379,7 @@ test('(SQL) pick param used twice', () => { name: 'insertUsersTwice', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); expect(interpolationResult).toEqual(expectedInterpolationResult); @@ -438,7 +438,7 @@ test('(SQL) pickSpread param', () => { name: 'insertUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); @@ -498,7 +498,7 @@ test('(SQL) pickSpread param used twice', () => { name: 'insertUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); @@ -537,7 +537,7 @@ test('(SQL) scalar param required and optional', () => { name: 'selectSomeUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); @@ -591,7 +591,7 @@ test('(SQL) pick param required', () => { name: 'insertUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); expect(interpolationResult).toEqual(expectedInterpolationResult); @@ -633,7 +633,7 @@ test('(SQL) array param required', () => { name: 'selectSomeUsers', }; - const queryIR = queryASTToIR(fileAST.queries[0]); + const queryIR = queryASTToIR(fileAST.queries[0], null); const interpolationResult = processSQLQueryIR(queryIR, parameters); const mappingResult = processSQLQueryIR(queryIR); diff --git a/packages/runtime/src/preprocessor-sql.ts b/packages/runtime/src/preprocessor-sql.ts index 096eac73..a08c521c 100644 --- a/packages/runtime/src/preprocessor-sql.ts +++ b/packages/runtime/src/preprocessor-sql.ts @@ -1,4 +1,9 @@ -import { assert, SQLQueryIR, TransformType } from '@pgtyped/parser'; +import { + assert, + SQLQueryIR, + TransformType, + InputParamTransform, +} from '@pgtyped/parser'; import { InterpolatedQuery, NestedParameters, @@ -11,6 +16,18 @@ import { Scalar, } from './preprocessor.js'; +function transformInputParam( + val: Scalar, + inputTypeTransform: InputParamTransform | undefined, +) { + switch (inputTypeTransform?.type) { + case 'stringify': + return JSON.stringify(val); + default: + return val; + } +} + /* Processes query AST formed by new parser from pure SQL files */ export const processSQLQueryIR = ( queryIR: SQLQueryIR, @@ -145,9 +162,15 @@ export const processSQLQueryIR = ( // Handle scalar transform const assignedIndex = i++; + const inputTypeTransform = + queryIR.inputParamTransforms?.[assignedIndex.toString()]; + if (passedParams) { const paramValue = passedParams[usedParam.name] as Scalar; - bindings.push(paramValue); + + // Transforms are only applied to scalars, because the intention is to remove + // the other transforms above. + bindings.push(transformInputParam(paramValue, inputTypeTransform)); } else { paramMapping.push({ name: usedParam.name, diff --git a/packages/runtime/src/preprocessor.ts b/packages/runtime/src/preprocessor.ts index b1adb354..147084a9 100644 --- a/packages/runtime/src/preprocessor.ts +++ b/packages/runtime/src/preprocessor.ts @@ -14,6 +14,13 @@ export interface ScalarParameter { assignedIndex: number; } +export interface InputTypeReferenceParameter { + type: 'inputTypeReference'; + name: string; + tableName: string; + assignedIndex: number; +} + export interface DictParameter { name: string; type: ParameterTransform.Pick; @@ -40,7 +47,8 @@ export type QueryParameter = | ScalarParameter | ScalarArrayParameter | DictParameter - | DictArrayParameter; + | DictArrayParameter + | InputTypeReferenceParameter; export interface InterpolatedQuery { query: string;