From 215354567a2803d93a5e4113ffa46824f05ca4b4 Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Fri, 1 Nov 2024 15:01:49 +0100 Subject: [PATCH 1/4] LDEV-4753 handle empty numeric values with CFUPDATE / CFINSERT https://luceeserver.atlassian.net/browse/LDEV-4753 --- .../main/java/lucee/runtime/tag/Insert.java | 19 +++-- .../main/java/lucee/runtime/tag/Update.java | 26 ++++--- test/tickets/LDEV4753.cfc | 69 +++++++++++------ test/tickets/LDEV4753_mysql.cfc | 75 +++++++++++++++++++ 4 files changed, 153 insertions(+), 36 deletions(-) create mode 100644 test/tickets/LDEV4753_mysql.cfc diff --git a/core/src/main/java/lucee/runtime/tag/Insert.java b/core/src/main/java/lucee/runtime/tag/Insert.java index 84b594ce94..52cb8ef50e 100755 --- a/core/src/main/java/lucee/runtime/tag/Insert.java +++ b/core/src/main/java/lucee/runtime/tag/Insert.java @@ -42,6 +42,7 @@ import lucee.runtime.ext.tag.TagImpl; import lucee.runtime.functions.displayFormatting.DecimalFormat; import lucee.runtime.op.Caster; +import lucee.runtime.type.Collection.Key; import lucee.runtime.type.QueryImpl; import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; @@ -236,7 +237,7 @@ public static Struct getMeta(DatasourceConnection dc, String tableQualifier, Str String name; while (columns.next()) { name = columns.getString("COLUMN_NAME"); - sct.setEL(name, new ColumnInfo(name, getInt(columns, "DATA_TYPE"), getBoolean(columns, "IS_NULLABLE"))); + sct.setEL(Caster.toKey(name), new ColumnInfo(name, getInt(columns, "DATA_TYPE"), getBoolean(columns, "NULLABLE"))); } } catch (SQLException sqle) { @@ -259,7 +260,7 @@ private static int getInt(ResultSet columns, String columnLabel) throws PageExce private static boolean getBoolean(ResultSet columns, String columnLabel) throws PageException, SQLException { try { - return columns.getBoolean(columnLabel); + return Caster.toBoolean(columns.getInt(columnLabel)); } catch (Exception e) { return Caster.toBooleanValue(columns.getObject(columnLabel)); @@ -292,9 +293,17 @@ private SQL createSQL(Struct meta) throws PageException { } names.append(field); values.append('?'); - ColumnInfo ci = (ColumnInfo) meta.get(field, null); - if (ci != null) items.add(new SQLItemImpl(form.get(field, null), ci.getType())); - else items.add(new SQLItemImpl(form.get(field, null))); + Key fieldKey = Caster.toKey(field); + ColumnInfo ci = (ColumnInfo) meta.get(fieldKey, null); + if (ci != null){ + Object val = form.get(fieldKey, null); + SQLItemImpl item = new SQLItemImpl(val, ci.getType()); + if (ci.isNullable() && StringUtil.isEmpty(val)) + item.setNulls(true); + items.add(item); + } else { + items.add(new SQLItemImpl(form.get(fieldKey, null))); + } } } if (items.size() == 0) return null; diff --git a/core/src/main/java/lucee/runtime/tag/Update.java b/core/src/main/java/lucee/runtime/tag/Update.java index ec7bc7f989..8b07596f1a 100755 --- a/core/src/main/java/lucee/runtime/tag/Update.java +++ b/core/src/main/java/lucee/runtime/tag/Update.java @@ -42,6 +42,7 @@ import lucee.runtime.ext.tag.TagImpl; import lucee.runtime.functions.displayFormatting.DecimalFormat; import lucee.runtime.op.Caster; +import lucee.runtime.type.Collection.Key; import lucee.runtime.type.QueryImpl; import lucee.runtime.type.Struct; import lucee.runtime.type.scope.Form; @@ -280,29 +281,36 @@ private SQL createSQL(DatasourceConnection dc, String[] keys, Struct meta) throw StringBuffer set = new StringBuffer(); StringBuffer where = new StringBuffer(); - ArrayList setItems = new ArrayList(); - ArrayList whereItems = new ArrayList(); + ArrayList setItems = new ArrayList(); + ArrayList whereItems = new ArrayList(); String field; for (int i = 0; i < fields.length; i++) { field = StringUtil.trim(fields[i], null); if (StringUtil.startsWithIgnoreCase(field, "form.")) field = field.substring(5); if (!field.equalsIgnoreCase("fieldnames")) { + Key fieldKey = Caster.toKey(field); if (ArrayUtil.indexOfIgnoreCase(keys, field) == -1) { if (set.length() == 0) set.append(" set "); else set.append(","); set.append(field); set.append("=?"); - ColumnInfo ci = (ColumnInfo) meta.get(field); - if (ci != null) setItems.add(new SQLItemImpl(form.get(field, null), ci.getType())); - else setItems.add(new SQLItemImpl(form.get(field, null))); + ColumnInfo ci = (ColumnInfo) meta.get(fieldKey); + if (ci != null){ + Object val = form.get(fieldKey, null); + SQLItemImpl item = new SQLItemImpl(val, ci.getType()); + if (ci.isNullable() && StringUtil.isEmpty(val)) item.setNulls(true); + setItems.add(item); + } else { + setItems.add(new SQLItemImpl(form.get(fieldKey, null))); + } } else { if (where.length() == 0) where.append(" where "); else where.append(" and "); where.append(field); where.append("=?"); - whereItems.add(new SQLItemImpl(form.get(field, null))); + whereItems.add(new SQLItemImpl(form.get(fieldKey, null))); } } } @@ -328,19 +336,19 @@ private SQL createSQL(DatasourceConnection dc, String[] keys, Struct meta) throw return new SQLImpl(sql.toString(), arrayMerge(setItems, whereItems)); } - private SQLItem[] arrayMerge(ArrayList setItems, ArrayList whereItems) { + private SQLItem[] arrayMerge(ArrayList setItems, ArrayList whereItems) { SQLItem[] items = new SQLItem[setItems.size() + whereItems.size()]; int index = 0; // Item int size = setItems.size(); for (int i = 0; i < size; i++) { - items[index++] = (SQLItem) setItems.get(i); + items[index++] = setItems.get(i); } // Where size = whereItems.size(); for (int i = 0; i < size; i++) { - items[index++] = (SQLItem) whereItems.get(i); + items[index++] = whereItems.get(i); } return items; } diff --git a/test/tickets/LDEV4753.cfc b/test/tickets/LDEV4753.cfc index 7fdd052ea2..4061864781 100644 --- a/test/tickets/LDEV4753.cfc +++ b/test/tickets/LDEV4753.cfc @@ -1,37 +1,67 @@ -component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { +component extends="org.lucee.cfml.test.LuceeTestCase" labels="mssql" { function beforeAll() { if(isNotSupported()) return; - request.mssql = getCredentials(); - request.mssql.storage = true; - variables.str = request.mssql; + var mssql = getCredentials(); + mssql.storage = true; + variables.datasource = mssql; tableCreation(); } + function afterAll() { + if(isNotSupported()) return; + return; + query datasource=variables.datasource{ + echo("DROP TABLE IF EXISTS LDEV4753"); + } + } + function run( testResults , testBox ) { - describe( title = "Test suite for LDEV-4753", body = function() { - it( title = "checking CFUPDATE for LDEV-4753", body = function( currentSpec ) { + describe( title = "Test suite for LDEV-4753 with mssql", body = function() { + it( title = "checking CFINSERT for LDEV-4753 with empty numeric cols", body = function( currentSpec ) { param name="form.id" default="1"; param name="form.myValue" default="LuceeTestCase"; param name="form.seqno" default=""; - expect( function() { - cfupdate(tableName = "cfupdatetbl" formFields = "form.id,form.myValue,form.seqno" datasource=str); - }).notToThrow(); + cfinsert(tableName = "LDEV4753" formFields = "form.id,form.myValue,form.seqno" datasource=variables.datasource); + checkTable( 1 ); + }); + + it( title = "checking CFUPDATE for LDEV-4753 with empty numeric cols", body = function( currentSpec ) { + param name="form.id" default="1"; + param name="form.myValue" default="LDEV-4753"; + param name="form.seqno" default=""; + cfupdate(tableName = "LDEV4753" formFields = "form.id,form.myValue,form.seqno" datasource=variables.datasource); + checkTable( 1 ); + + form.seqno="3"; + form.myValue=""; + cfupdate(tableName = "LDEV4753" formFields = "form.id,form.myValue,form.seqno" datasource=variables.datasource); + checkTable( 1 ); }); }); } - private function tableCreation() { - query datasource=str{ - echo("DROP TABLE IF EXISTS cfupdatetbl"); + query datasource=variables.datasource{ + echo("DROP TABLE IF EXISTS LDEV4753"); } - query datasource=str{ - echo(" - create table cfupdatetbl (id numeric(18, 0) primary key,myValue nvarchar(50),seqno numeric(18, 0))" - ); + query datasource=variables.datasource{ + echo("create table LDEV4753 (id numeric(18, 0) primary key,myValue nvarchar(50),seqno numeric(18, 0))"); } } + private function checkTable( id ){ + var params = { + id = {value: arguments.id, sql_type: "numeric" } + } + query name="local.q" datasource=variables.datasource params=params { + echo("select * from LDEV4753 where id = :id"); + } + systemOutput( q, true ); + expect ( q.recordcount ).toBe( 1, "recordcount" ); + loop list="id,myvalue,seqno" item="local.c"{ + expect( q[ c ] ).toBe( form[ c ] ); + } + } private boolean function isNotSupported() { var cred=getCredentials(); @@ -42,10 +72,5 @@ component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { return server.getDatasource("mssql"); } - function afterAll() { - if(isNotSupported()) return; - query datasource=str{ - echo("DROP TABLE IF EXISTS cfupdatetbl"); - } - } + } diff --git a/test/tickets/LDEV4753_mysql.cfc b/test/tickets/LDEV4753_mysql.cfc new file mode 100644 index 0000000000..ee72fc3a16 --- /dev/null +++ b/test/tickets/LDEV4753_mysql.cfc @@ -0,0 +1,75 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="mysql" { + function beforeAll() { + if(isNotSupported()) return; + var mysql = getCredentials(); + mysql.storage = true; + variables.datasource = mysql; + tableCreation(); + } + + function afterAll() { + if(isNotSupported()) return; + return; + query datasource=variables.datasource{ + echo("DROP TABLE IF EXISTS LDEV4753"); + } + } + + function run( testResults , testBox ) { + describe( title = "Test suite for LDEV-4753 with MSSQL", body = function() { + it( title = "checking CFINSERT for LDEV-4753 with empty numeric cols", body = function( currentSpec ) { + param name="form.id" default="1"; + param name="form.myValue" default="LuceeTestCase"; + param name="form.seqno" default=""; + cfinsert(tableName = "LDEV4753" formFields = "form.id,form.myValue,form.seqno" datasource=variables.datasource); + checkTable( 1 ); + }); + it( title = "checking CFUPDATE for LDEV-4753 with empty numeric cols", body = function( currentSpec ) { + param name="form.id" default="1"; + param name="form.myValue" default="LDEV-4753"; + param name="form.seqno" default=""; + cfupdate(tableName = "LDEV4753" formFields = "form.id,form.myValue,form.seqno" datasource=variables.datasource); + checkTable( 1 ); + form.seqno="3"; + form.myValue=""; + cfupdate(tableName = "LDEV4753" formFields = "form.id,form.myValue,form.seqno" datasource=variables.datasource); + checkTable( 1 ); + }); + }); + } + + + private function tableCreation() { + query datasource=variables.datasource{ + echo("DROP TABLE IF EXISTS LDEV4753"); + } + query datasource=variables.datasource{ + echo("create table LDEV4753 (id numeric(18, 0) primary key,myValue nvarchar(50),seqno numeric(18, 0))"); + } + } + + private function checkTable( id ){ + var params = { + id = {value: arguments.id, sql_type: "numeric" } + } + query name="local.q" datasource=variables.datasource params=params { + echo("select * from LDEV4753 where id = :id"); + } + systemOutput( q, true ); + expect ( q.recordcount ).toBe( 1 ); + loop list="id,myvalue,seqno" item="local.c"{ + expect( q[ c ] ).toBe( form[ c ] ); + } + } + + private boolean function isNotSupported() { + var cred=getCredentials(); + return isNull(cred) || structCount(cred)==0; + } + + private struct function getCredentials() { + return server.getDatasource("mysql"); + } + + +} From fc531166ee8960b9d99703af2a524ddb14513a89 Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Fri, 1 Nov 2024 15:16:24 +0100 Subject: [PATCH 2/4] fix passing null to getColumns --- core/src/main/java/lucee/runtime/tag/Insert.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/lucee/runtime/tag/Insert.java b/core/src/main/java/lucee/runtime/tag/Insert.java index 52cb8ef50e..029cb194e6 100755 --- a/core/src/main/java/lucee/runtime/tag/Insert.java +++ b/core/src/main/java/lucee/runtime/tag/Insert.java @@ -232,7 +232,7 @@ public static Struct getMeta(DatasourceConnection dc, String tableQualifier, Str Struct sct = new StructImpl(); try { DatabaseMetaData md = dc.getConnection().getMetaData(); - columns = md.getColumns(tableQualifier, tableOwner, tableName, null); + columns = md.getColumns(StringUtil.emptyAsNull(tableQualifier, true), StringUtil.emptyAsNull(tableOwner, true), tableName, "%"); String name; while (columns.next()) { From 672ba6e548890d3dfe2a8c8493b6b59bcfbb38dc Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Fri, 1 Nov 2024 15:33:42 +0100 Subject: [PATCH 3/4] cleanup --- test/tickets/LDEV4753.cfc | 1 - test/tickets/LDEV4753_mysql.cfc | 1 - 2 files changed, 2 deletions(-) diff --git a/test/tickets/LDEV4753.cfc b/test/tickets/LDEV4753.cfc index 4061864781..b7e6726e9c 100644 --- a/test/tickets/LDEV4753.cfc +++ b/test/tickets/LDEV4753.cfc @@ -56,7 +56,6 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="mssql" { query name="local.q" datasource=variables.datasource params=params { echo("select * from LDEV4753 where id = :id"); } - systemOutput( q, true ); expect ( q.recordcount ).toBe( 1, "recordcount" ); loop list="id,myvalue,seqno" item="local.c"{ expect( q[ c ] ).toBe( form[ c ] ); diff --git a/test/tickets/LDEV4753_mysql.cfc b/test/tickets/LDEV4753_mysql.cfc index ee72fc3a16..eb4ef95efd 100644 --- a/test/tickets/LDEV4753_mysql.cfc +++ b/test/tickets/LDEV4753_mysql.cfc @@ -56,7 +56,6 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="mysql" { echo("select * from LDEV4753 where id = :id"); } systemOutput( q, true ); - expect ( q.recordcount ).toBe( 1 ); loop list="id,myvalue,seqno" item="local.c"{ expect( q[ c ] ).toBe( form[ c ] ); } From cf38039b88f4fecc1fd9513ca49c94958416e8c9 Mon Sep 17 00:00:00 2001 From: Zac Spitzer Date: Fri, 1 Nov 2024 16:04:45 +0100 Subject: [PATCH 4/4] cleanup --- test/tickets/LDEV4753.cfc | 1 - test/tickets/LDEV4753_mysql.cfc | 1 - 2 files changed, 2 deletions(-) diff --git a/test/tickets/LDEV4753.cfc b/test/tickets/LDEV4753.cfc index b7e6726e9c..81c3afe250 100644 --- a/test/tickets/LDEV4753.cfc +++ b/test/tickets/LDEV4753.cfc @@ -9,7 +9,6 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="mssql" { function afterAll() { if(isNotSupported()) return; - return; query datasource=variables.datasource{ echo("DROP TABLE IF EXISTS LDEV4753"); } diff --git a/test/tickets/LDEV4753_mysql.cfc b/test/tickets/LDEV4753_mysql.cfc index eb4ef95efd..44ab2677ba 100644 --- a/test/tickets/LDEV4753_mysql.cfc +++ b/test/tickets/LDEV4753_mysql.cfc @@ -9,7 +9,6 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="mysql" { function afterAll() { if(isNotSupported()) return; - return; query datasource=variables.datasource{ echo("DROP TABLE IF EXISTS LDEV4753"); }