From c470a545270b334f7441cc8ce7c49e85b5240bed Mon Sep 17 00:00:00 2001 From: Jovan Popovic Date: Mon, 24 Oct 2016 22:07:50 -0700 Subject: [PATCH] JSON CRUD generator Added JSON CRUD generator sample code. --- .../features/json/code-generator/README.md | 202 ++++++++ .../code-generator/generate-json-crud.sql | 470 ++++++++++++++++++ .../temporal/product-catalog/.gitignore | 3 +- 3 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 samples/features/json/code-generator/README.md create mode 100644 samples/features/json/code-generator/generate-json-crud.sql diff --git a/samples/features/json/code-generator/README.md b/samples/features/json/code-generator/README.md new file mode 100644 index 00000000..559cd0f9 --- /dev/null +++ b/samples/features/json/code-generator/README.md @@ -0,0 +1,202 @@ +# T-SQL Procedures that generate T-SQL JSON CRUD procedures + +This project contains a set of T-SQL scripts that generate: +- Stored procedures that INSERT, UPDATE, or MERGE input JSON text into table +- Stored procedures that generate SELECT statements that generate JSON from SQL tables. + +### Contents + +[About this sample](#about-this-sample)
+[Setup](#setup)
+[Generate](#generate)
+[Modify generated source](#modify)
+ + + +## About this sample + +- **Applies to:** SQL Server 2016 (or higher), Azure SQL Database +- **Programming Language:** Transact-SQL +- **Authors:** Jovan Popovic + + + +## Setup + +To generate procedures, apply script in generate-json-crud.sql file. + + +## Generate procedures + + +** GENERATE CRUD Functions for WWI tables. ** + +```sql +declare @SchemaName sysname = 'Application' --> Name of the table where we want to insert JSON +declare @TableName sysname = 'People' --> Name of the table schema where we want to insert JSON +declare @JsonColumns nvarchar(max) = '|CustomFields|' --> List of pipe-separated NVARCHAR(MAX) column names that contain JSON text +declare @IgnoredColumns nvarchar(max) = N'LastEditedBy' --> List of comma-separated columns that should not be imported + +print (codegen.GenerateJsonCreateProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonRetrieveProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonUpdateProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonUpsertProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) + +GO + +declare @SchemaName sysname = 'Sales' --> Name of the table where we want to insert JSON +declare @TableName sysname = 'Orders' --> Name of the table schema where we want to insert JSON +declare @JsonColumns nvarchar(max) = '||' --> List of pipe-separated NVARCHAR(MAX) column names that contain JSON text +declare @IgnoredColumns nvarchar(max) = N'LastEditedBy,LastEditedWhen' --> List of comma-separated columns that should not be imported + + +print (codegen.GenerateJsonCreateProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonRetrieveProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonUpdateProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonUpsertProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) + +GO + +declare @SchemaName sysname = 'Application' --> Name of the table where we want to insert JSON +declare @TableName sysname = 'Countries' --> Name of the table schema where we want to insert JSON +declare @JsonColumns nvarchar(max) = '||' --> List of pipe-separated NVARCHAR(MAX) column names that contain JSON text, e.g. '|AdditionalContactInfo|Demographics|' +declare @IgnoredColumns nvarchar(max) = N'LastEditedBy' --> List of comma-separated columns that should not be imported + + +print (codegen.GenerateJsonCreateProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonRetrieveProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonUpdateProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonUpsertProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) + +GO + +declare @SchemaName sysname = 'Application' --> Name of the table where we want to insert JSON +declare @TableName sysname = 'Cities' --> Name of the table schema where we want to insert JSON +declare @JsonColumns nvarchar(max) = '||' --> List of pipe-separated NVARCHAR(MAX) column names that contain JSON text, e.g. '|AdditionalContactInfo|Demographics|' +declare @IgnoredColumns nvarchar(max) = N'Location,LastEditedBy' --> List of comma-separated columns that should not be imported + + +print (codegen.GenerateJsonCreateProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonRetrieveProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonUpdateProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) +print (codegen.GenerateJsonUpsertProcedure('Website', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) + + +``` + + +**GENERATE CRUD Functions for Custom table.** + +```sql +DROP TABLE IF EXISTS dbo.Product +GO +CREATE TABLE dbo.Product( + ProductID int IDENTITY PRIMARY KEY, + Name nvarchar(50) NOT NULL, + Color nvarchar(15) NULL, + Size nvarchar(5) NULL, + Price money NOT NULL, + [Special JSON chars: " \ / + ] int NULL, + [Special sql chars [[ " ]] + ] int NULL, + Data nvarchar(4000) NULL, + Tags nvarchar(4000) NULL, + DateCreated datetime2 NOT NULL DEFAULT(GETDATE()) +) +GO + +declare @SchemaName sysname = 'dbo' --> Name of the table where we want to insert JSON +declare @TableName sysname = 'Product' --> Name of the table schema where we want to insert JSON +declare @JsonColumns nvarchar(max) = '|Data|Tags|' --> List of pipe-separated NVARCHAR(MAX) column names that contain JSON text, e.g. '|AdditionalContactInfo|Demographics|' +declare @IgnoredColumns nvarchar(max) = N'DateCreated' --> List of comma-separated columns that should not be imported + +print (codegen.GenerateJsonCreateProcedure('dbo', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) + +print (codegen.GenerateJsonRetrieveProcedure('dbo', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) + +print (codegen.GenerateJsonUpdateProcedure('dbo', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) + +print (codegen.GenerateJsonUpsertProcedure('dbo', @SchemaName, @TableName, @JsonColumns, @IgnoredColumns)) + +``` + +## Modify generated code + +You can create your own functions codegen.GenerateProcedureHead and codegen.GenerateProcedureTail to generate custom header and footer for every generated procedure, e.g.: + +```sql + +DROP FUNCTION IF EXISTS codegen.GenerateProcedureHead +GO +CREATE FUNCTION codegen.GenerateProcedureHead(@Table sysname, @JsonParam sysname) +RETURNS NVARCHAR(max) +AS BEGIN + + Declare @ret nvarchar(max) = ' + SET XACT_ABORT ON; + + DECLARE @HelpMessage nvarchar(max) = N''JSON '+ @Table +' data is invalid. +Execute SELECT TOP 1 * FROM ' + @Table + ' FOR JSON PATH to see an example of required JSON structure.''; + + IF ISJSON('+ @JsonParam + ') = 0 + BEGIN + PRINT @HelpMessage; + THROW 51000, N'''+ @JsonParam + ' must be valid JSON data'', 1; + RETURN 1; + END; + + BEGIN TRY + + BEGIN TRAN; + + '; + + RETURN @ret + +END +GO + +GO +DROP FUNCTION IF EXISTS codegen.GenerateProcedureTail +GO +CREATE FUNCTION codegen.GenerateProcedureTail(@Table sysname) +RETURNS NVARCHAR(max) +AS BEGIN + + Declare @ret nvarchar(max) = ' + + IF @@ROWCOUNT = 0 + BEGIN + PRINT N''Warning: No valid '+@Table+' data found''; + PRINT @HelpMessage; + END; + + COMMIT; + + END TRY + BEGIN CATCH + PRINT @HelpMessage; + PRINT ERROR_MESSAGE(); + + THROW 51000, N''Valid JSON was supplied but does not match the '+@Table+' array structure'', 2; + + IF XACT_STATE() <> 0 ROLLBACK TRAN; + + RETURN 1; + END CATCH; +'; + + RETURN @ret + +END +GO +``` +## Code of Conduct +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## License +These samples and templates are all licensed under the MIT license. See the license.txt file in the root. + +## Questions +Email questions to: [sqlserversamples@microsoft.com](mailto: sqlserversamples@microsoft.com). diff --git a/samples/features/json/code-generator/generate-json-crud.sql b/samples/features/json/code-generator/generate-json-crud.sql new file mode 100644 index 00000000..321c5442 --- /dev/null +++ b/samples/features/json/code-generator/generate-json-crud.sql @@ -0,0 +1,470 @@ +/******************************************************************************************************************************************************** +* +* Functions that generate CREATE PROCEDURE script that inserts, updates, retreives, and merges +* input JSON parameter in a table. +* Keys in JSON object must match columns in table. Use FOR JSON to generate JSON from table. +* Author: Jovan Popovic +* +********************************************************************************************************************************************************/ + +DROP SCHEMA IF EXISTS codegen +GO +CREATE SCHEMA codegen +GO + +DROP FUNCTION IF EXISTS codegen.QNAME +GO +CREATE FUNCTION codegen.QNAME(@name sysname) +RETURNS NVARCHAR(300) AS +BEGIN + RETURN(IIF(@name like '%[^a-zA-Z0-9]%', QUOTENAME(@name),@name)); +END +GO +DROP FUNCTION IF EXISTS codegen.JSON_ESCAPE +GO +CREATE FUNCTION codegen.JSON_ESCAPE(@name sysname) +RETURNS NVARCHAR(300) AS +BEGIN + RETURN(IIF(@name like '%[^a-zA-Z0-9]%', '"'+STRING_ESCAPE(@name, 'json')+'"',@name)); +END +GO +DROP FUNCTION IF EXISTS codegen.GenerateProcedureHead +GO +CREATE FUNCTION codegen.GenerateProcedureHead(@Table sysname, @JsonParam sysname) +RETURNS NVARCHAR(max) +AS BEGIN + + RETURN '' + +END +GO + +GO +DROP FUNCTION IF EXISTS codegen.GenerateProcedureTail +GO +CREATE FUNCTION codegen.GenerateProcedureTail(@Table sysname) +RETURNS NVARCHAR(max) +AS BEGIN + + RETURN '' + +END +GO +GO +DROP FUNCTION IF EXISTS +codegen.GetTableColumns +GO +CREATE FUNCTION +codegen.GetTableColumns(@SchemaName sysname, @TableName sysname, @JsonColumns nvarchar(max), @IgnoredColumns nvarchar(max)) +RETURNS TABLE +AS RETURN ( + select + col.name as ColumnName, + col.column_id ColumnId, + typ.name as ColumnType, + -- create type with size based on type name and size + case typ.name + when 'char' then '(' + cast(col.max_length as varchar(10))+ ')' + when 'nchar' then '(' + cast(col.max_length/2 as varchar(10))+ ')' + when 'nvarchar' then (IIF(col.max_length=-1 or CHARINDEX(col.name, @JsonColumns,0) > 0, '(MAX)', '(' + cast(col.max_length/2 as varchar(10))+ ')')) + when 'varbinary' then (IIF(col.max_length=-1 , '(MAX)', '(' + cast(col.max_length as varchar(10))+ ')')) + when 'varchar' then (IIF(col.max_length=-1, '(MAX)', '(' + cast(col.max_length as varchar(10))+ ')')) + when 'decimal' then '(' + cast(col.precision as varchar(10))+ ',' + cast(col.scale as varchar(10)) + ')' + when 'datetimeoffset' then '(' + cast(col.scale as varchar(10))+ ')' + when 'numeric' then '(' + cast(col.precision as varchar(10))+ ',' + cast(col.scale as varchar(10)) + ')' + when 'datetime2' then '(' + cast(col.scale as varchar(10))+ ')' + else '' + end as StringSize, + -- if column is not nullable, add Strict mode in JSON + case + when col.is_nullable = 1 then '$.' else 'strict $.' + end Mode, + CHARINDEX(col.name, @JsonColumns,0) as IsJson, + i.is_primary_key IsPK, + IIF(col.is_identity = 0 + and col.is_computed = 0 + and col.is_hidden = 0 + and col.is_rowguidcol = 0 + and generated_always_type = 0 + and (sm.text IS NULL OR sm.text NOT LIKE '(NEXT VALUE FOR%') + and LOWER(typ.name) NOT IN ('text', 'ntext', 'sql_variant', 'image','hierarchyid','geometry','geography'), + 1,0) as IsDataColumn +from sys.columns col + join sys.types typ + on col.system_type_id = typ.system_type_id AND col.user_type_id = typ.user_type_id + LEFT join sys.index_columns ic + ON ic.object_id = col.object_id AND col.column_id = ic.column_id + LEFT join sys.indexes i + ON i.object_id = ic.object_id AND i.index_id = ic.index_id + LEFT JOIN sys.syscomments SM ON col.default_object_id = SM.id +where col.object_id = object_id(codegen.QNAME(@SchemaName) + '.' + codegen.QNAME(@TableName)) +and col.name NOT IN (SELECT value COLLATE Latin1_General_CI_AS FROM STRING_SPLIT(@IgnoredColumns, ',')) +) +GO + +GO +DROP FUNCTION IF EXISTS +codegen.GetPkColumns +GO +CREATE FUNCTION +codegen.GetPkColumns(@SchemaName sysname, @TableName sysname) +RETURNS TABLE +AS RETURN ( + select * FROM codegen.GetTableColumns(@SchemaName, @TableName,'','') + where IsPK = 1 +) +GO +DROP FUNCTION IF EXISTS +codegen.GetTableDefinition +GO +CREATE FUNCTION +codegen.GetTableDefinition(@SchemaName sysname, @TableName sysname, @JsonColumns nvarchar(max), @IgnoredColumns nvarchar(max)) +RETURNS TABLE +AS RETURN ( + select * FROM codegen.GetTableColumns(@SchemaName, @TableName,@JsonColumns,@IgnoredColumns) + where IsDataColumn = 1 +) +GO +GO +DROP FUNCTION IF EXISTS codegen.GetOpenJsonSchema +GO +CREATE FUNCTION codegen.GetOpenJsonSchema (@SchemaName sysname, @TableName sysname, @JsonColumns nvarchar(max), @IgnoredColumns nvarchar(max), @WithPk bit = 0) +RETURNS NVARCHAR(MAX) +AS +BEGIN +DECLARE @JsonSchema NVARCHAR(MAX) = ''; + +with columns as ( +select ColumnId, ColumnName, ColumnType, StringSize, IsJson, Mode +from codegen.GetTableDefinition(@SchemaName, @TableName, @JsonColumns, @IgnoredColumns) +union all +select ColumnId, ColumnName, ColumnType, StringSize, 0 as IsJson, 'strict $.' as Mode +from codegen.GetPkColumns(@SchemaName, @TableName) +where @WithPk = 1 +) +SELECT @JsonSchema = +@JsonSchema + ' + ' + codegen.QNAME(ColumnName) + ' ' + ColumnType + StringSize + + IIF(ISNULL(SESSION_CONTEXT(N'CODEGEN:EXPLICIT.JSON.PATH'),'no')='yes', + (' N''' + Mode + codegen.JSON_ESCAPE(ColumnName) +'''' +IIF(IsJson>0, ' AS JSON', '') + ',' ), + IIF(codegen.JSON_ESCAPE(ColumnName) <> codegen.QNAME(ColumnName) OR CHARINDEX('strict', Mode)>0, + (' N''' + Mode + codegen.JSON_ESCAPE(ColumnName) +'''' +IIF(IsJson>0, ' AS JSON', '') + ',' ), + IIF(IsJson>0, ' AS JSON', '') + ',')) +from columns +order by ColumnId; +RETURN @JsonSchema + +END + +GO +DROP FUNCTION IF EXISTS +codegen.GenerateJsonCreateProcedure +GO +CREATE FUNCTION +codegen.GenerateJsonCreateProcedure(@ProcSchemaName sysname, @SchemaName sysname, @TableName sysname, @JsonColumns nvarchar(max), @IgnoredColumns nvarchar(max)) +RETURNS NVARCHAR(MAX) +AS BEGIN +declare @JsonParam sysname = '@'+@TableName+'Json' +declare @JsonSchema nvarchar(max) = codegen.GetOpenJsonSchema (@SchemaName, @TableName, @JsonColumns, @IgnoredColumns, 0); + +-- Generate list of column names ordered by columnid +declare @TableSchema nvarchar(max) = ''; +select @TableSchema = @TableSchema + codegen.QNAME(ColumnName) + ',' +from codegen.GetTableDefinition(@SchemaName, @TableName, @JsonColumns, @IgnoredColumns) +order by ColumnId + +SET @TableSchema = SUBSTRING(@TableSchema, 0, LEN(@TableSchema)) --> remove last comma + +-- Generate list of column names ordered by columnid +declare @OutputPks nvarchar(max) = ''; + +select @OutputPks = @OutputPks + ' INSERTED.'+ codegen.QNAME(ColumnName) + ',' +from codegen.GetPkColumns(@SchemaName, @TableName) +order by ColumnId + +SET @OutputPks = SUBSTRING(@OutputPks, 0, LEN(@OutputPks)) --> remove last comma + + + +declare @Result nvarchar(max) = +N'GO +DROP PROCEDURE IF EXISTS ' + codegen.QNAME( @ProcSchemaName) + '.' + codegen.QNAME('Insert' + @TableName + 'FromJson') + ' +GO +CREATE PROCEDURE ' + codegen.QNAME( @ProcSchemaName) + '.' + codegen.QNAME('Insert' + @TableName + 'FromJson') + '(' + @JsonParam + ' NVARCHAR(MAX)) +WITH EXECUTE AS OWNER +AS BEGIN +' + + codegen.GenerateProcedureHead(@TableName, @JsonParam) ++ +' INSERT INTO ' + codegen.QNAME( @SchemaName) + '.' + codegen.QNAME(@TableName) + '(' + @TableSchema + ') + OUTPUT ' + @OutputPks + ' + SELECT ' + @TableSchema + ' + FROM OPENJSON(' + @JsonParam + ') + WITH (' + @JsonSchema + ')' + + codegen.GenerateProcedureTail(@TableName) ++ +' +END' + +RETURN REPLACE(@Result,',)',')') +END + +GO + +GO +DROP FUNCTION IF EXISTS +codegen.GenerateJsonUpdateProcedure +GO +CREATE FUNCTION +codegen.GenerateJsonUpdateProcedure(@ProcSchemaName sysname, @SchemaName sysname, @TableName sysname, @JsonColumns nvarchar(max), @IgnoredColumns nvarchar(max)) +RETURNS NVARCHAR(MAX) +AS BEGIN +declare @JsonParam sysname = '@'+@TableName+'Json' +declare @JsonSchema nvarchar(max) = codegen.GetOpenJsonSchema (@SchemaName, @TableName, @JsonColumns, @IgnoredColumns, 1); + +-- Generate list of column names ordered by columnid +declare @TableSchema nvarchar(max) = ''; + +select @TableSchema = @TableSchema + CHAR(10) + ' ' + codegen.QNAME(ColumnName) + ' = json.' + codegen.QNAME(ColumnName) + ',' +from codegen.GetTableDefinition(@SchemaName, @TableName, @JsonColumns, @IgnoredColumns) +order by ColumnId + +SET @TableSchema = SUBSTRING(@TableSchema, 0, LEN(@TableSchema)) --> remove last comma + + +-- Generate list of column names ordered by columnid +declare @RowFilter nvarchar(max) = ''; + +select @RowFilter = @RowFilter + CHAR(10) + ' '+ codegen.QNAME( @SchemaName) + '.' + codegen.QNAME(@TableName) +'.' + codegen.QNAME(ColumnName) + ' = json.' + codegen.QNAME(ColumnName) + ',' +from codegen.GetPkColumns(@SchemaName, @TableName) +order by ColumnId + +SET @RowFilter = SUBSTRING(@RowFilter, 0, LEN(@RowFilter)) --> remove last comma + +declare @Result nvarchar(max) = +N'GO +DROP PROCEDURE IF EXISTS ' + codegen.QNAME( @ProcSchemaName) + '.' + codegen.QNAME('Update' + @TableName + 'FromJson') + ' +GO +CREATE PROCEDURE ' + codegen.QNAME( @ProcSchemaName) + '.' + codegen.QNAME('Update' + @TableName + 'FromJson') +'(' + @JsonParam + ' NVARCHAR(MAX)) +WITH EXECUTE AS OWNER +AS BEGIN' + + codegen.GenerateProcedureHead(@TableName, @JsonParam) ++ +' UPDATE ' + codegen.QNAME( @SchemaName) + '.' + codegen.QNAME(@TableName) + ' SET' + @TableSchema + ' + FROM OPENJSON(' + @JsonParam + ') + WITH (' + @JsonSchema + ') as json + WHERE ' + @RowFilter + ' +' + + codegen.GenerateProcedureTail(@TableName) ++ +' +END' + +RETURN REPLACE(@Result,',)',')') +END + +GO + + +GO +DROP FUNCTION IF EXISTS +codegen.GenerateJsonRetrieveProcedure +GO +CREATE FUNCTION +codegen.GenerateJsonRetrieveProcedure(@ProcSchemaName sysname, @SchemaName sysname, @TableName sysname, @JsonColumns nvarchar(max), @IgnoredColumns nvarchar(max)) +RETURNS NVARCHAR(MAX) +AS BEGIN + +declare @Columns nvarchar(max) = ''; + +with all_columns(ColumnId, ColumnName, ColumnType, StringSize, IsJson, Mode) AS( +select ColumnId, ColumnName, ColumnType, StringSize, IsJson, Mode from codegen.GetTableDefinition(@SchemaName, @TableName, @JsonColumns, @IgnoredColumns) +union all +select ColumnId, ColumnName, ColumnType, StringSize, 0 as IsJson, 'strict $.' as Mode from codegen.GetPkColumns(@SchemaName, @TableName) +) +select @Columns = @Columns + codegen.QNAME(ColumnName) + ','-- + ColumnType + StringSize --+ + -- ' N''' + Mode + '"' + STRING_ESCAPE(ColumnName, 'json') + '"''' +IIF(IsJson>0, ' AS JSON', '') + ',' +from all_columns +order by ColumnId + +set @Columns = SUBSTRING(@Columns, 0, LEN(@Columns)) + +-- Generate list of column names ordered by columnid +declare @Parameters nvarchar(max) = ''; + +select @Parameters = @Parameters + '@' + ColumnName + ' ' + ColumnType + ',' +from codegen.GetPkColumns(@SchemaName, @TableName) +order by ColumnId + + +-- Generate list of column names ordered by columnid +declare @RowFilter nvarchar(max) = ''; + +select @RowFilter = @RowFilter + codegen.QNAME( @SchemaName) + '.' + codegen.QNAME(@TableName) + '.' + codegen.QNAME(ColumnName) + ' = @' + ColumnName + ' AND ' +from codegen.GetPkColumns(@SchemaName, @TableName) +order by ColumnId + +SET @RowFilter = SUBSTRING(@RowFilter, -3, LEN(@RowFilter)) --> remove last comma + +declare @Result nvarchar(max) = +N'GO +DROP FUNCTION IF EXISTS ' + codegen.QNAME( @ProcSchemaName) + '.' + codegen.QNAME('Retrieve' + @TableName + 'AsJson') + ' +GO +CREATE FUNCTION ' + codegen.QNAME( @ProcSchemaName) + '.' + codegen.QNAME('Retrieve' + @TableName + 'AsJson') + '(' + @Parameters + ') +RETURNS NVARCHAR(MAX) +AS BEGIN + RETURN( SELECT ' + @Columns + ' + FROM ' + codegen.QNAME( @SchemaName) + '.' + codegen.QNAME(@TableName) + ' + WHERE ' + @RowFilter + ' + FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) +END' + +RETURN REPLACE(@Result,',)',')') +END + +GO + +GO +DROP FUNCTION IF EXISTS +codegen.GenerateJsonDeleteProcedure +GO +CREATE FUNCTION +codegen.GenerateJsonDeleteProcedure(@ProcSchemaName sysname, @SchemaName sysname, @TableName sysname, @JsonColumns nvarchar(max), @IgnoredColumns nvarchar(max)) +RETURNS NVARCHAR(MAX) +AS BEGIN + +declare @Columns nvarchar(max) = ''; + +with all_columns(ColumnId, ColumnName, ColumnType, StringSize, IsJson, Mode) AS( +select ColumnId, ColumnName, ColumnType, StringSize, IsJson, Mode from codegen.GetTableDefinition(@SchemaName, @TableName, @JsonColumns, @IgnoredColumns) +union all +select ColumnId, ColumnName, ColumnType, StringSize, 0 as IsJson, 'strict $.' as Mode from codegen.GetPkColumns(@SchemaName, @TableName) +) +select @Columns = @Columns + codegen.QNAME(ColumnName) + ',' +from all_columns +order by ColumnId + +-- Generate list of column names ordered by columnid +declare @Parameters nvarchar(max) = ''; + +select @Parameters = @Parameters + '@' + ColumnName + ' ' + ColumnType + ',' +from codegen.GetPkColumns(@SchemaName, @TableName) +order by ColumnId + + +-- Generate list of column names ordered by columnid +declare @RowFilter nvarchar(max) = ''; + +select @RowFilter = @RowFilter + codegen.QNAME( @SchemaName) + '.' + codegen.QNAME(@TableName) + '.' + codegen.QNAME(ColumnName) + ' = @' + ColumnName + ' AND ' +from codegen.GetPkColumns(@SchemaName, @TableName) +order by ColumnId + +SET @RowFilter = SUBSTRING(@RowFilter, -3, LEN(@RowFilter)) --> remove last comma + +declare @Result nvarchar(max) = +N'GO +DROP PROCEDURE IF EXISTS ' + codegen.QNAME( @SchemaName) + '.' + codegen.QNAME('Delete' + @TableName) + ' +GO +CREATE PROCEDURE ' + codegen.QNAME( @SchemaName) + '.' + codegen.QNAME('Delete' + @TableName) + '(' + @Parameters + ') +WITH EXECUTE AS OWNER +AS BEGIN + DELETE ' + codegen.QNAME( @SchemaName) + '.' + codegen.QNAME(@TableName) + ' + WHERE ' + @RowFilter + ' +END' + +RETURN REPLACE(@Result,',)',')') +END + +GO + +GO +DROP FUNCTION IF EXISTS +codegen.GenerateJsonUpsertProcedure +GO +CREATE FUNCTION +codegen.GenerateJsonUpsertProcedure(@ProcSchemaName sysname, @SchemaName sysname, @TableName sysname, @JsonColumns nvarchar(max), @IgnoredColumns nvarchar(max)) +RETURNS NVARCHAR(MAX) +AS BEGIN +declare @JsonParam sysname = '@'+@TableName+'Json' +declare @JsonSchema nvarchar(max) = codegen.GetOpenJsonSchema (@SchemaName, @TableName, @JsonColumns, @IgnoredColumns, 1); + +-- Generate list of column names ordered by columnid +declare @TableSchema nvarchar(max) = ''; + +select @TableSchema = @TableSchema + CHAR(10) + ' ' + codegen.QNAME(ColumnName) + ',' +from codegen.GetTableDefinition(@SchemaName, @TableName, @JsonColumns, @IgnoredColumns) +order by ColumnId + +SET @TableSchema = SUBSTRING(@TableSchema, 0, LEN(@TableSchema)) --> remove last comma + + +-- Generate list of column names ordered by columnid +declare @TableSchemaWithAlias nvarchar(max) = ''; + +select @TableSchemaWithAlias = @TableSchemaWithAlias + CHAR(10) + ' json.' + codegen.QNAME(ColumnName) + ',' +from codegen.GetTableDefinition(@SchemaName, @TableName, @JsonColumns, @IgnoredColumns) +order by ColumnId + +SET @TableSchemaWithAlias = SUBSTRING(@TableSchemaWithAlias, 0, LEN(@TableSchemaWithAlias)) --> remove last comma + + +declare @TableUpdateColumns nvarchar(max) = ''; + +select @TableUpdateColumns = @TableUpdateColumns + CHAR(10) + ' ' + codegen.QNAME(ColumnName) + ' = json.' + codegen.QNAME(ColumnName) + ',' +from codegen.GetTableDefinition(@SchemaName, @TableName, @JsonColumns, @IgnoredColumns) +order by ColumnId + +SET @TableUpdateColumns = SUBSTRING(@TableUpdateColumns, 0, LEN(@TableUpdateColumns)) --> remove last comma + + +-- Generate list of column names ordered by columnid +declare @RowFilter nvarchar(max) = ''; +declare @PkColumns nvarchar(max) = ''; +declare @PkColumnsAndTypes nvarchar(max) = ''; + +select +@PkColumns = @PkColumns + 'INSERTED.' + codegen.QNAME(ColumnName) + ',', +@PkColumnsAndTypes = @PkColumnsAndTypes + codegen.QNAME(ColumnName) + ' INT,', +@RowFilter = @RowFilter + CHAR(10) + ' '+ codegen.QNAME( @SchemaName) + '.' + codegen.QNAME(@TableName) +'.' + codegen.QNAME(ColumnName) + ' = json.' + codegen.QNAME(ColumnName) + ',' +from codegen.GetPkColumns(@SchemaName, @TableName) +order by ColumnId + +SET @PkColumns = SUBSTRING(@PkColumns, 0, LEN(@PkColumns)) --> remove last comma +SET @PkColumnsAndTypes = SUBSTRING(@PkColumnsAndTypes, 0, LEN(@PkColumnsAndTypes)) --> remove last comma +SET @RowFilter = SUBSTRING(@RowFilter, 0, LEN(@RowFilter)) --> remove last comma + +declare @Result nvarchar(max) = +N'GO +DROP PROCEDURE IF EXISTS ' + codegen.QNAME( @ProcSchemaName) + '.' + codegen.QNAME('Upsert' + @TableName + 'FromJson') + ' +GO +CREATE PROCEDURE ' + codegen.QNAME( @ProcSchemaName) + '.' + codegen.QNAME('Upsert' + @TableName + 'FromJson') +'(' + @JsonParam + ' NVARCHAR(MAX)) +WITH EXECUTE AS OWNER +AS BEGIN +' + + codegen.GenerateProcedureHead(@TableName, @JsonParam) ++ +'MERGE INTO ' + codegen.QNAME( @SchemaName) + '.' + codegen.QNAME(@TableName) + ' + USING ( SELECT * + FROM OPENJSON(' + @JsonParam + ') + WITH (' + @JsonSchema + ')) as json + ON (' + @RowFilter + ') + WHEN MATCHED THEN + UPDATE SET' + @TableUpdateColumns + + + ' + WHEN NOT MATCHED THEN + INSERT (' + @TableSchema + ') + VALUES (' + @TableSchemaWithAlias + ');' + +-- OUTPUT ' + @PkColumns + ' +-- INTO @ChildRows; +--' + + codegen.GenerateProcedureTail(@TableName) ++ +' +END' + + +RETURN REPLACE(@Result,',)',')') +END + +GO \ No newline at end of file diff --git a/samples/features/temporal/product-catalog/.gitignore b/samples/features/temporal/product-catalog/.gitignore index bb5a0f9a..4cec0eb0 100644 --- a/samples/features/temporal/product-catalog/.gitignore +++ b/samples/features/temporal/product-catalog/.gitignore @@ -5,4 +5,5 @@ bin/* obj/* *.sln *.log -Properties/PublishProfiles/* \ No newline at end of file +Properties/PublishProfiles/* +project.lock.json \ No newline at end of file