diff --git a/docs/functions.md b/docs/functions.md index 471eb6fe..ee647ab5 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -23,8 +23,11 @@ - `replace` _[boolean]_ - create or replace function - `window` _[boolean]_ - window function - `behavior` _[string]_ - `IMMUTABLE`, `STABLE`, or `VOLATILE` - - `onNull` _[boolean]_ - `RETURNS NULL ON NULL INPUT` + - `strict` _[boolean]_ - `RETURNS NULL ON NULL INPUT` + - `onNull` _[string]_ (deprecated) - alias for `strict`, also accepts `NULL` or `CALLED` - `parallel` _[string]_ - `UNSAFE`, `RESTRICTED`, or `SAFE` + - `cost` _[number]_ - estimated execution cost + - `rows` _[number]_ - estimated number of result rows - `definition` _[string]_ - definition of function **Reverse Operation:** `dropFunction` diff --git a/index.d.ts b/index.d.ts index 70455548..d675afb1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -267,8 +267,12 @@ export interface FunctionOptions { replace?: boolean window?: boolean behavior?: 'IMMUTABLE' | 'STABLE' | 'VOLATILE' - onNull?: boolean + onNull?: boolean | 'NULL' | 'CALLED' + strict?: boolean + security?: 'DEFINER' | 'INVOKER' parallel?: 'UNSAFE' | 'RESTRICTED' | 'SAFE' + cost?: number + rows?: number } interface TriggerOptions { diff --git a/lib/operations/functions.js b/lib/operations/functions.js index 33783d83..7160ba9c 100644 --- a/lib/operations/functions.js +++ b/lib/operations/functions.js @@ -29,7 +29,12 @@ function createFunction(mOptions) { window, behavior = 'VOLATILE', onNull, - parallel + strict, + security, + parallel, + cost, + rows, + support } = functionOptions; const options = []; if (behavior) { @@ -45,12 +50,24 @@ function createFunction(mOptions) { if (window) { options.push('WINDOW'); } - if (onNull) { + if (onNull === true || onNull === 'NULL' || strict) { options.push('RETURNS NULL ON NULL INPUT'); } + if (security) { + options.push(`SECURITY ${security}`); + } if (parallel) { options.push(`PARALLEL ${parallel}`); } + if (typeof cost !== 'undefined') { + options.push(`COST ${cost}`); + } + if (typeof rows !== 'undefined') { + options.push(`ROWS ${rows}`); + } + if (support) { + options.push(`SUPPORT ${support}`); + } const replaceStr = replace ? ' OR REPLACE' : ''; const paramsStr = formatParams(functionParams, mOptions); diff --git a/test/functions-test.js b/test/functions-test.js new file mode 100644 index 00000000..eefc02c9 --- /dev/null +++ b/test/functions-test.js @@ -0,0 +1,105 @@ +const { expect } = require('chai'); +const Functions = require('../lib/operations/functions'); +const { options1, options2 } = require('./utils'); + +describe('lib/operations/functions', () => { + const params = ['integer', { name: 'arg2', mode: 'in', type: 'integer' }]; + describe('.create', () => { + it('check schemas can be used', () => { + const args = [ + { schema: 'mySchema', name: 'f' }, + params, + { + returns: 'integer', + language: 'plpgsql', + onNull: 'CALLED' + }, + `BEGIN + return $1 + arg2; +END;` + ]; + const sql1 = Functions.createFunction(options1)(...args); + const sql2 = Functions.createFunction(options2)(...args); + expect(sql1).to.equal( + `CREATE FUNCTION "mySchema"."f"(integer, in "arg2" integer) + RETURNS integer + AS $pg1$BEGIN + return $1 + arg2; +END;$pg1$ + VOLATILE + LANGUAGE plpgsql;` + ); + expect(sql2).to.equal( + `CREATE FUNCTION "my_schema"."f"(integer, in "arg2" integer) + RETURNS integer + AS $pg1$BEGIN + return $1 + arg2; +END;$pg1$ + VOLATILE + LANGUAGE plpgsql;` + ); + }); + + it('check param shorthands work', () => { + const args = [ + { schema: 'mySchema', name: 'f' }, + ['integer', 'IN arg2 integer'], + { + returns: 'integer', + language: 'sql', + behavior: 'IMMUTABLE', + strict: true + }, + `SELECT $1 + arg2;` + ]; + const sql1 = Functions.createFunction(options1)(...args); + const sql2 = Functions.createFunction(options2)(...args); + expect(sql1).to.equal( + `CREATE FUNCTION "mySchema"."f"(integer, IN arg2 integer) + RETURNS integer + AS $pg1$SELECT $1 + arg2;$pg1$ + IMMUTABLE + LANGUAGE sql + RETURNS NULL ON NULL INPUT;` + ); + expect(sql2).to.equal( + `CREATE FUNCTION "my_schema"."f"(integer, IN arg2 integer) + RETURNS integer + AS $pg1$SELECT $1 + arg2;$pg1$ + IMMUTABLE + LANGUAGE sql + RETURNS NULL ON NULL INPUT;` + ); + }); + + it('check all PG12 options are supported', () => { + const sql = Functions.createFunction(options1)( + 'myFunction', + ['a integer', 'b real'], + { + returns: 'integer', + language: 'plpgsql', + behavior: 'STABLE', + onNull: 'NULL', + security: 'DEFINER', + parallel: 'RESTRICTED', + cost: 0, + rows: 7 + }, + 'blah-blubb' + ); + expect(sql).to.equal( + `CREATE FUNCTION "myFunction"(a integer, b real) + RETURNS integer + AS $pg1$blah-blubb$pg1$ + STABLE + LANGUAGE plpgsql + RETURNS NULL ON NULL INPUT + SECURITY DEFINER + PARALLEL RESTRICTED + COST 0 + ROWS 7;` + ); + }); + }); +});