# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: marian.edu@acorn.ro-20210131101452-ui20dacrs58vsdlj
# target_branch: sftp://medu@xfer.goldencode.com/opt/fwd/4384g/
# testament_sha1: f5f7eca50742231137f87677da03f7f60bdd4f38
# timestamp: 2021-02-01 10:09:59 +0200
# base_revision_id: marian.edu@acorn.ro-20210130114536-\
# y9e0nzy27pjk8fut
#
# Begin patch
=== modified file 'build.gradle'
--- build.gradle 2020-09-15 14:26:57 +0000
+++ build.gradle 2020-11-16 21:38:16 +0000
@@ -64,6 +64,11 @@
** 037 HC 20200726 Initial implementation of SPREADSHEET widget and related changes.
** CA 20200914 Added aspectjtools to fwdConvert group.
** CA 20200915 Upgraded to Jetty 9.4.22.
+** CA 20201011 Added RoaringBitmap, a fast access bitmap for integer or long values, which can be used
+** instead of hash sets.
+** ECF 20201116 Changed version of fwd-h2 to 1.4.200-5. Note the naming convention change, by which the
+** last component of the name is now the fwd-h2 revision number, rather than a sortable
+** date.
*/
/*
@@ -307,7 +312,7 @@
fwdConvertServer group: 'org.jboss.logging', name: 'jboss-logging', version: '3.1.0.GA'
fwdConvertServer group: 'antlr', name: 'antlr', version: '2.7.7'
- fwdConvertServer group: 'com.goldencode', name: 'fwd-h2', version: '1.4.200-20200727'
+ fwdConvertServer group: 'com.goldencode', name: 'fwd-h2', version: '1.4.200-5'
fwdConvertServer group: 'javax.transaction', name: 'jta', version: '1.1'
fwdConvertClient (group: 'com.google.guava', name: 'guava', version: '19.0') {
@@ -353,6 +358,7 @@
fwdClientServer group: 'org.shredzone.acme4j', name: 'acme4j-client', version: '0.10'
fwdClientServer group: 'org.shredzone.acme4j', name: 'acme4j-utils', version: '0.10'
fwdClientServer group: 'com.esotericsoftware', name: 'reflectasm', version: '1.11.3'
+ fwdClientServer group: 'org.roaringbitmap', name: 'RoaringBitmap', version: '0.9.0'
fwdServer group: 'org.apache.tuscany.sdo', name: 'tuscany-sdo-impl', version: '1.1.1'
fwdServer group: 'org.apache.tuscany.sdo', name: 'tuscany-sdo-tools', version: '1.1.1'
=== modified file 'build.xml'
--- build.xml 2020-09-07 16:23:31 +0000
+++ build.xml 2021-01-05 13:24:19 +0000
@@ -195,7 +195,9 @@
** 101 VVT 20200203 Added cursor UI resources (.cur files) to jar location.
** 102 OM 20200108 DMO Impl generation TRPL dropped from runtime support.
** 103 HC 20200726 Initial implementation of SPREADSHEET widget and related changes.
-*/
+** HC 20201020 Removed ext sheet sources from javadoc target. Javadoc is handled by the sheet
+** project itself.
+**
-->
@@ -485,13 +486,62 @@
+
+
+
+
+ copy.type == prog.assign or
+ copy.type == prog.equals or
+ copy.type == prog.lt or
+ copy.type == prog.lte or
+ copy.type == prog.gt or
+ copy.type == prog.gte
+
+
+
+ child = copy.firstChild
+ ch1type = ecw.getCompatibilityClass(child)
+
+ child = child.nextSibling
+ ch1type != null and
+ !ch1type.equals("unknown") and
+ !ch1type.equals("basedatatype") and
+ !child.isAnnotation("read-only attribute helper")
+
+ ch2type = ecw.getCompatibilityClass(child)
+
+ !ch1type.equals(ch2type)
+ !isRuntimeConfig()
+
+
+ printfln("WARNING: Incompatible data types in expression or assignment. The generated code may not compile or may work incorrectly.")
+
+
+
+ printfln("Additional info: ch1type=%s ch2type=%s\n%s", ch1type, ch2type, copy.dumpTree(true))
+
+ isRuntimeConfig()
+
+ compileError(223, "Incompatible data types in expression or assignment")
+
+
+
+
+
+
+
-
+
-
+
=== modified file 'rules/annotations/database_general.rules'
--- rules/annotations/database_general.rules 2020-09-10 09:54:53 +0000
+++ rules/annotations/database_general.rules 2021-01-21 22:49:45 +0000
@@ -5,9 +5,9 @@
** Module : database_general.rules
** Abstract : database related annotations
**
-** Copyright (c) 2005-2020, Golden Code Development Corporation.
+** Copyright (c) 2005-2021, Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ __JPRM__ ____________________________Description_____________________________
+** _#_ _I_ __Date__ __JPRM__ ___________________________________Description___________________________________
** 001 ECF 20051207 @23763 Created initial version. Miscellaneous rules
** for annotating various database language
** statements and functions.
@@ -91,10 +91,8 @@
** connect options.
** 035 ECF 20170820 Added support for read-xml and write-xml methods.
** 036 OM 20170823 Fixed DISABLE TRIGGERS statement.
-** 037 GES 20170827 CONNECT_TEXT nodes can have quoted text in which the quotes were
-** being doubled.
-** 038 ECF 20171025 Added -ct (max connection retries) to supported CONNECT statement
-** options.
+** 037 GES 20170827 CONNECT_TEXT nodes can have quoted text in which the quotes were being doubled.
+** 038 ECF 20171025 Added -ct (max connection retries) to supported CONNECT statement options.
** 039 OM 20171215 Force fields to have alias only if the quick-delete has subqueries.
** 040 ECF 20180118 API name change in SchemaWorker.
** 041 OM 20180309 The id-related functions are converted earlier into corresponding
@@ -108,8 +106,7 @@
** currently is not implemented.
** 046 ECF 20181208 Added support for read-json and write-json methods.
** 047 CA 20181221 Fixed FIND-CURRENT lock literals.
-** 048 ECF 20190214 Rearrange BUF_{COPY|COMP} statement child nodes for easier downstream
-** conversion.
+** 048 ECF 20190214 Rearrange BUF_{COPY|COMP} statement child nodes for easier downstream conversion.
** CA 20190323 FOR EACH ... DELETE. blocks conversion to EMPTY TEMP-TABLE must be
** done only after buffer scopes are computed.
** 049 OM 20190327 Renamed DataSource to avoid conflicts with DataSet source.
@@ -121,6 +118,7 @@
** 054 IAS 20200212 Added support for new CONNECT options.
** 055 ECF 20200906 New ORM implementation.
** 056 CA 20200910 Added BUFFER:SERIALIZE-ROW() method conversion.
+** OM 20210120 Kept resource type parameter in order to validate it at runtime.
-->
parent.type == prog.meth_logical
-
oldtype = parent.getAnnotation("oldtype")oldtype == prog.kw_read_xsc or
@@ -922,36 +919,9 @@
pos1 = this.indexPos
-
- (pos1 == 0 and oldtype != prog.kw_serialzr) or (pos1 == 1 and oldtype == prog.kw_serialzr)
-
-
- oldtype == prog.kw_read_xml or
- oldtype == prog.kw_read_jsn or
- oldtype == prog.kw_read_xsc
- copy.setHidden(true)
-
-
-
- oldtype == prog.kw_wr_xml or
- oldtype == prog.kw_wr_json or
- oldtype == prog.kw_wr_xmlsc or
- oldtype == prog.kw_serialzr
-
- javaname = ecw.expressionType(#(com.goldencode.ast.Aast) this.nextSibling)
-
- javaname == "memptr" or javaname == "longchar"
- copy.setHidden(true)
-
-
-
-
-
-
+
(pos1 >= 2 and oldtype != prog.kw_serialzr) or
((pos1 == 0 or pos1 >= 3) and oldtype == prog.kw_serialzr)
putNote("use-method-peerid", true)
=== modified file 'rules/annotations/legacy_services.rules'
--- rules/annotations/legacy_services.rules 2020-10-08 20:26:22 +0000
+++ rules/annotations/legacy_services.rules 2021-01-13 21:04:41 +0000
@@ -12,7 +12,7 @@
** 002 CA 20190703 Added support for WebHandler services.
** 003 CA 20200514 Added support for SOAP web services.
** 004 GES 20200723 Added address attribute for REST.
-** 005 CA 20201008 Annotate any temp-table, dataset, parameter, internal procedure and program which must be
+** CA 20201008 Annotate any temp-table, dataset, parameter, internal procedure and program which must be
** generated into the legacy open client proxy Java program.
*/
-->
=== modified file 'rules/annotations/ocx_conversion.rules'
--- rules/annotations/ocx_conversion.rules 2020-09-07 16:23:31 +0000
+++ rules/annotations/ocx_conversion.rules 2021-01-13 21:04:41 +0000
@@ -51,9 +51,11 @@
** 20200608 Added NODECLICK, BEFORELABELEDIT, AFTERLABELEDIT event triggers.
** 012 SBI 20200619 Changed NODECLICK, BEFORELABELEDIT, AFTERLABELEDIT to be according with FWD
** widget events naming convention, AFTERLABELEDIT can update the label's value.
-** 013 SBI 20200722 Added mapping for child, previous, next, firstsibling, lastsibling.
+** SBI 20200722 Added mapping for child, previous, next, firstsibling, lastsibling.
** HC 20200726 Improved OCX conversion of COM properties.
** HC 20200827 Fixed conversion of assignment of COM properties with multiple indexes.
+** CA 20201109 Expose the CALENDAR:VALUE as a 'CalendarValue' attribute which follows the 4GL's datetime
+** string representation, and not ISO-8601.
*/
-->
map6 = create("java.util.HashMap")
- map6.put("value" , "DateTimeValue")
+ map6.put("value" , "CalendarValue")map6.put("checkbox" , "Checked")map6.put("font" , "FontInfo")map6.put("format" , "FormatStyle")
=== modified file 'rules/annotations/record_scoping.rules'
--- rules/annotations/record_scoping.rules 2019-05-30 23:38:09 +0000
+++ rules/annotations/record_scoping.rules 2020-10-30 14:02:14 +0000
@@ -5,9 +5,9 @@
** Module : record_scoping.rules
** Abstract : calculates record scopes
**
-** Copyright (c) 2005-2019, Golden Code Development Corporation.
+** Copyright (c) 2005-2020, Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ __JPRM__ ________________Description_________________
+** _#_ _I_ __Date__ __JPRM__ _________________________________Description__________________________________
** 001 GES 20050919 @22811 Calculates record scopes in a Progress
** compatible manner, including support for
** all of the implicit scope expansion rules.
@@ -61,6 +61,8 @@
** 019 CA 20190128 Track buffer's static state (if OO).
** 020 CA 20190513 Fixed buffer usage from super-classes, in OO.
** 021 CA 20190530 Fixed staging of buffer parameters for OO methods or constructors.
+** 022 ECF 20201029 Create a buffer scope for NEW SHARED defined buffers, in case the buffer is
+** not used elsewhere in the procedure where it was defined.
*/
-->
+
type == prog.block and getNoteBoolean("recordScoping")
buf.pushScope(copy)
@@ -167,18 +168,18 @@
-
+
+
- ((type == prog.define_buffer and this.getChildAt(0).type == prog.kw_shared) or
- type == prog.define_temp_table or
+ ((type == prog.define_buffer and this.getImmediateChild(prog.kw_shared, null) != null) or
+ type == prog.define_temp_table or
type == prog.define_work_table) and not ancestor(prog.interface_def, -1)
-
- type == prog.define_buffer and this.getChildAt(0).type == prog.kw_shared
+
+ type == prog.define_buffer and this.getImmediateChild(prog.kw_shared, null) != null
ref = copy.getImmediateChild(prog.kw_for, null)ref.downPath("TEMP_TABLE")
ref = ref.getImmediateChild(prog.temp_table, null)
=== modified file 'rules/convert/builtin_functions.rules'
--- rules/convert/builtin_functions.rules 2020-09-07 16:23:31 +0000
+++ rules/convert/builtin_functions.rules 2021-01-13 21:04:41 +0000
@@ -7,7 +7,7 @@
**
** Copyright (c) 2005-2020, Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ __JPRM__ ____________________________Description_____________________________
+** _#_ _I_ __Date__ __JPRM__ ___________________________________Description___________________________________
** 001 GES 20050721 @21759 Good working version which supports a wide
** range of the most common builtin functions
** including all type conversion, math, string
@@ -176,6 +176,8 @@
** GES 20200506 Changed the name of DYNAMIC-ENUM().
** SBI 20200529 Implemented LOAD-PICTURE.
** 102 CA 20200811 SUPER() function has as first argument the caller's return type.
+** EVL 20201216 Adding function to interrupt metafile recording.
+** OM 20201120 Added DATA-SOURCE-MODIFIED function. Changed converted ERROR signature.
*/
-->
@@ -265,15 +267,15 @@
ftype == prog.kw_mf_gfsd or ftype == prog.kw_mf_gfsr or ftype == prog.kw_mf_gph or
ftype == prog.kw_mf_gpn or ftype == prog.kw_mf_gpw or ftype == prog.kw_mf_gtw or
ftype == prog.kw_mf_gxy or ftype == prog.kw_mf_gzf or ftype == prog.kw_mf_init or
- ftype == prog.kw_mf_mpdf or ftype == prog.kw_mf_p2mu or ftype == prog.kw_mf_rp or
- ftype == prog.kw_mf_sf or ftype == prog.kw_mf_sfc or ftype == prog.kw_mf_sfh or
- ftype == prog.kw_mf_si or ftype == prog.kw_mf_sia or ftype == prog.kw_mf_sla or
- ftype == prog.kw_mf_slc or ftype == prog.kw_mf_slm or ftype == prog.kw_mf_sls or
- ftype == prog.kw_mf_snp or ftype == prog.kw_mf_sntl or ftype == prog.kw_mf_spf or
- ftype == prog.kw_mf_spn or ftype == prog.kw_mf_spnp or ftype == prog.kw_mf_spnt or
- ftype == prog.kw_mf_spo or ftype == prog.kw_mf_sr or ftype == prog.kw_mf_sta or
- ftype == prog.kw_mf_stc or ftype == prog.kw_mf_sts or ftype == prog.kw_mf_sxy or
- ftype == prog.kw_mf_szf
+ ftype == prog.kw_mf_ir or ftype == prog.kw_mf_mpdf or ftype == prog.kw_mf_p2mu or
+ ftype == prog.kw_mf_rp or ftype == prog.kw_mf_sf or ftype == prog.kw_mf_sfc or
+ ftype == prog.kw_mf_sfh or ftype == prog.kw_mf_si or ftype == prog.kw_mf_sia or
+ ftype == prog.kw_mf_sla or ftype == prog.kw_mf_slc or ftype == prog.kw_mf_slm or
+ ftype == prog.kw_mf_sls or ftype == prog.kw_mf_snp or ftype == prog.kw_mf_sntl or
+ ftype == prog.kw_mf_spf or ftype == prog.kw_mf_spn or ftype == prog.kw_mf_spnp or
+ ftype == prog.kw_mf_spnt or ftype == prog.kw_mf_spo or ftype == prog.kw_mf_sr or
+ ftype == prog.kw_mf_sta or ftype == prog.kw_mf_stc or ftype == prog.kw_mf_sts or
+ ftype == prog.kw_mf_sxy or ftype == prog.kw_mf_szf
bRet = true
@@ -360,6 +362,7 @@
fwdId2Mf.put(prog.kw_mf_gxy , "getXY")fwdId2Mf.put(prog.kw_mf_gzf , "getZoomFactor")fwdId2Mf.put(prog.kw_mf_init , "initialize")
+ fwdId2Mf.put(prog.kw_mf_ir , "interruptRecording")fwdId2Mf.put(prog.kw_mf_mpdf , "makePdf")fwdId2Mf.put(prog.kw_mf_p2mu , "pix2MfUnits")
@@ -552,12 +555,18 @@
dbimport = true
+
+ ftype == prog.kw_data_sm
+ methodText = "DataSourceModifiable.isDataSourceModified"
+ dbimport = true
+
+
ftype == prog.kw_date
methodText = "date"methodType = java.constructor
-
+
ftype == prog.kw_datetime
methodText = "datetime"methodType = java.constructor
@@ -567,7 +576,7 @@
methodText = "datetimetz"methodType = java.constructor
-
+
ftype == prog.kw_day
methodText = "date.day"
@@ -756,14 +765,13 @@
extent = deref.getAnnotation("oldextent")
-
+
extent == null or extent == 0
methodText = "0"methodType = java.num_literal
-
+
(extent != null and extent == -1) or
(copy.parent.isAnnotation("wrap") and
@@ -1341,13 +1349,6 @@
dbimport = true
- ftype == prog.kw_error
- methodType = java.method_call
- methodText = "isError"
-
- dbimport = true
-
-
ftype == prog.kw_rejected
methodType = java.method_callmethodText = "rejected"
@@ -1655,8 +1656,7 @@
raiseError
- errmsg = sprintf("Unsupported builtin function %s.",
- this.lookupTokenName(ftype))
+ errmsg = sprintf("Unsupported builtin function %s.", this.lookupTokenName(ftype))
printfln("%s\n%s", errmsg, this.dumpTree(true))
=== modified file 'rules/convert/legacy_services.rules'
--- rules/convert/legacy_services.rules 2020-10-08 20:26:22 +0000
+++ rules/convert/legacy_services.rules 2021-01-08 17:01:46 +0000
@@ -5,10 +5,11 @@
** Module : legacy_services.rules
** Abstract : conversion related to legacy proxy client code.
**
-** Copyright (c) 2020, Golden Code Development Corporation.
+** Copyright (c) 2020-2021, Golden Code Development Corporation.
**
** _#_ _I_ __Date__ _______________________________________Description________________________________________
** 001 CA 20201007 Created initial version.
+** CA 20210108 Fixed a typo when generating the Java proxy code for a temp-table field definition.
*/
-->
@@ -366,7 +367,7 @@
createJavaAst(java.string, #(java.lang.String) propAst.getAnnotation("historical"), ref)
- isNote("extent")
+ propAst.isAnnotation("extent")
createJavaAst(java.num_literal, sprintf("%s", propAst.getAnnotation("extent")), ref)
=== modified file 'rules/convert/menu_generator.xml'
--- rules/convert/menu_generator.xml 2020-03-23 11:35:33 +0000
+++ rules/convert/menu_generator.xml 2020-10-24 00:11:03 +0000
@@ -7,7 +7,7 @@
**
** Copyright (c) 2015-2020, Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ ________________________________Description_________________________________
+** _#_ _I_ __Date__ _______________________________________Description_______________________________________
** 001 VIG 20141101 Initial version.
** 002 VIG 20150204 Added SUB-MENU label support, removeQuotes are replaced by
** ecw.progressToJavaString() method call.
@@ -18,13 +18,14 @@
** 005 OM 20150918 Fixed historical/java names.
** 006 VIG 20160621 Added labels collection to uiStrings file.
** 007 GES 20171017 Added hexadecimal literal support.
-** 008 CA 20180308 Fixed classname conversion - it was using an aready converted javaname
+** 008 CA 20180308 Fixed classname conversion - it was using an already converted javaname
** instead of the legacy name, so it was not converting properly.
** 009 GES 20180910 Resolved ambiguous method call (it did not fail previously but the correct
** method was not being called).
** 010 CA 20181208 Use progressToJavaString to emit the TITLE option.
** 011 CA 20200412 Added incremental conversion support.
** 012 SBI 20200322 Added label not null rule for processing menu items.
+** 013 OM 20201016 Added NPE guard for uninitialized uiStrings.
*/
-->
@@ -462,7 +463,9 @@
- execLib("collect_ui_strings", uiStrings)
+ uiStrings != null
+ execLib("collect_ui_strings", uiStrings)
+
=== modified file 'rules/convert/method_definitions.rules'
--- rules/convert/method_definitions.rules 2020-09-10 09:54:53 +0000
+++ rules/convert/method_definitions.rules 2021-01-13 21:04:41 +0000
@@ -7,7 +7,7 @@
**
** Copyright (c) 2018-2020, Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ _______________________________Description_________________________________
+** _#_ _I_ __Date__ _______________________________________Description_______________________________________
** 001 GES 20181213 First version.
** 002 OM 20181218 Added generics for object getters.
** 003 GES 20190118 Changed "access" annotation to "access-mode" for methods. This is consistent
@@ -29,7 +29,7 @@
** CA 20200514 Emit the LegacySignature at the external program's 'execute' method and all its internal
** entries.
** 009 CA 20200804 Emit bulk setter and getter for an extent property.
-** 010 CA 20200910 Fixed LegacyParameter annotation problem when DEFINE PARAMETER appears somewhere in a
+** CA 20200910 Fixed LegacyParameter annotation problem when DEFINE PARAMETER appears somewhere in a
** nested block.
*/
-->
=== modified file 'rules/convert/methods_attributes.rules'
--- rules/convert/methods_attributes.rules 2020-09-10 09:54:53 +0000
+++ rules/convert/methods_attributes.rules 2021-01-13 21:04:41 +0000
@@ -495,10 +495,16 @@
** CA 20200503 Added ERROR:ERROR-STRING, QUERY:CACHE and BUFFER:IS-MULTI-TENANT.
** SVL 20200509 Added ALIGNMENT attribute.
** 222 SBI 20200619 Added NEW-LABEL attribute for the tree widget.
-** 223 GES 20200627 Renamed Coordinates interface to be more generic. Added the undocumented
+** GES 20200627 Renamed Coordinates interface to be more generic. Added the undocumented
** LAST-EVENT:SET-LASTKEY() method. Added the LAST-EVENT:COLUMN/ROW attributes
** support. Added the BUFFER-FIELD:DEFAULT-VALUE attribute.
-** 224 CA 20200910 Added BUFFER:SERIALIZE-ROW() method conversion.
+** CA 20200910 Added BUFFER:SERIALIZE-ROW() method conversion.
+** CA 20201109 Expose the CALENDAR:VALUE as a 'CalendarValue' attribute which follows the 4GL's
+** datetime string representation, and not ISO-8601.
+** SVL 20201225 MIN-HEIGHT-CHARS is unwrapped using MinHeightCharsInterface. MAX-HEIGHT-CHARS is
+** applicable only to Window widget
+** OM 20201120 Conversion changes for multiple methods/attributes related to datasets and xml
+** serialization.
*/
-->
@@ -613,6 +619,7 @@
list.put(prog.kw_aud_ev_c, execLib("cr_descr", "ClientPrincipal" , "getAuditEventContext" , "setAuditEventContext" , true ))list.put(prog.kw_authen_f, execLib("cr_descr", "ClientPrincipal" , "authenticationFailed" , null , true ))list.put(prog.kw_auto_com, execLib("cr_descr", "Widget" , "isAutoCompletion" , "setAutoCompletion" , true ))
+ list.put(prog.kw_auto_del, execLib("cr_descr", "Buffer" , "autoDelete" , "autoDelete" , true ))list.put(prog.kw_auto_end, execLib("cr_descr", "Button" , "isAutoEndKey" , "setAutoEndKey" , true ))list.put(prog.kw_auto_go , execLib("cr_descr", "Button" , "isAutoGo" , "setAutoGo" , true ))list.put(prog.kw_auto_ind, execLib("cr_descr", "Editor" , "isAutoIndent" , "setAutoIndent" , true ))
@@ -668,7 +675,7 @@
list.put(prog.kw_cur_qry , execLib("cr_descr", "DataRelation" , "getCurrentQuery" , null , true ))list.put(prog.kw_cvt_3d_c, execLib("cr_descr", "ImageSupport" , "getConvert3D" , "setConvert3D" , true ))list.put(prog.kw_data_scm, execLib("cr_descr", "Buffer" , "dataSourceCompleteMap" , null , true ))
- list.put(prog.kw_data_sm , execLib("cr_descr", "DataSet" , "isDataSourceModified" , "setDataSourceModified" , true ))
+ list.put(prog.kw_data_sm , execLib("cr_descr", "DataSourceModifiable" , "isDataSourceModified" , "setDataSourceModified" , true ))list.put(prog.kw_data_src, execLib("cr_descr", "Buffer" , "dataSource" , null , true ))list.put(prog.kw_data_sri, execLib("cr_descr", "Buffer" , "dataSourceRowid" , "dataSourceRowid" , true ))list.put(prog.kw_dataset , execLib("cr_descr", "Buffer" , "dataSet" , null , true ))
@@ -676,7 +683,7 @@
list.put(prog.kw_db_list , execLib("cr_descr", "ClientPrincipal" , "getDbList" , null , true ))list.put(prog.kw_deblank , execLib("cr_descr", "Deblank" , "isDeblank" , "setDeblank" , true ))list.put(prog.kw_debug , execLib("cr_descr", "Debugger" , "debug" , null , true ))
- list.put(prog.kw_decimals, execLib("cr_descr", "BufferField" , "getDecimals" , null , true ))
+ list.put(prog.kw_decimals, execLib("cr_descr", "BufferField" , "getDecimals" , "setDecimals" , true ))list.put(prog.kw_def_comm, execLib("cr_descr", "Transaction" , "isDefaultCommit" , "setDefaultCommit" , true ))list.put(prog.kw_def_str , execLib("cr_descr", "BufferField" , "getDefaultString" , "setDefaultString" , true ))list.put(prog.kw_def_val , execLib("cr_descr", "BufferField" , "getDefaultValue" , "" , true ))
@@ -711,14 +718,14 @@
list.put(prog.kw_end_f_d , execLib("cr_descr", "Droppable" , "endFileDrop" , null , true ))list.put(prog.kw_ent_tlst, execLib("cr_descr", "LogManager" , "getEntryTypesList" , null , true ))list.put(prog.kw_entry , execLib("cr_descr", "CommonListWidget" , "entry" , null , true ))
- list.put(prog.kw_error , execLib("cr_descr", "Error" , "isError" , "setError" , true ))
+ list.put(prog.kw_error , execLib("cr_descr", "Error" , "error" , "error" , true ))list.put(prog.kw_err_str , execLib("cr_descr", "ErrorString" , "errorString" , "changeErrorString" , true ))list.put(prog.kw_evt_proc, execLib("cr_descr", "EventProcedure" , "getEventProcedure" , "setEventProcedure" , true ))list.put(prog.kw_expand , execLib("cr_descr", "RadioSet" , "isExpand" , "setExpand" , true ))list.put(prog.kw_export_p, execLib("cr_descr", "ClientPrincipal" , "exportPrincipal" , null , true ))list.put(prog.kw_f_key_h , execLib("cr_descr", "DataRelation" , "getForeignKeyHidden" , "setForeignKeyHidden" , true ))list.put(prog.kw_fetch_sr, execLib("cr_descr", "Browse" , "fetchSelectedRow" , null , true ))
- list.put(prog.kw_fill_mod, execLib("cr_descr", "Buffer" , "fillMode" , "fillMode" , true ))
+ list.put(prog.kw_fill_mod, execLib("cr_descr", "Fillable" , "fillMode" , "fillMode" , true ))list.put(prog.kw_fill_wst, execLib("cr_descr", "DataSource" , "getFillWhereString" , "setFillWhereString" , true ))list.put(prog.kw_fill , execLib("cr_descr", "Fillable" , "fill" , null , true ))list.put(prog.kw_filled , execLib("cr_descr", "Rectangle" , "getFilled" , "setFilled" , true ))
@@ -762,7 +769,7 @@
list.put(prog.kw_img_only, execLib("cr_descr", "ImageOnly" , "isImageOnly" , "setImageOnly" , true ))list.put(prog.kw_in_hndl , execLib("cr_descr", "Call" , "getInHandle" , "setInHandle" , true ))list.put(prog.kw_index , execLib("cr_descr", "Indexed" , "getIndex" , "setIndex" , true ))
- list.put(prog.kw_init_c_p, execLib("cr_descr", "ClientPrincipal" , "initialize" , null , true ))
+ list.put(prog.kw_init_c_p, execLib("cr_descr", "ClientPrincipal" , "initialize" , null , true ))list.put(prog.kw_initiate, execLib("cr_descr", "Debugger" , "initiate" , null , true ))list.put(prog.kw_inner_c , execLib("cr_descr", "ScrollbarHorizontalElement" , "getInnerChars" , "setInnerChars" , true ))list.put(prog.kw_inner_l , execLib("cr_descr", "InnerLines" , "getInnerLines" , "setInnerLines" , true ))
@@ -780,7 +787,7 @@
list.put(prog.kw_lab_font, execLib("cr_descr", "BrowseElement" , "getLabelFont" , "setLabelFont" , true ))list.put(prog.kw_labels , execLib("cr_descr", "Labels" , "isLabels" , "setLabels" , true ))list.put(prog.kw_large , execLib("cr_descr", "Editor" , "isLarge" , "setLarge" , true ))
- list.put(prog.kw_last_bat, execLib("cr_descr", "Buffer" , "isLastBatch" , "setLastBatch" , true ))
+ list.put(prog.kw_last_bat, execLib("cr_descr", "Buffer" , "lastBatch" , "lastBatch" , true ))list.put(prog.kw_last_of , execLib("cr_descr", "Query" , "isLastOfGroup" , null , true ))list.put(prog.kw_last_ti , execLib("cr_descr", "FieldGroup" , "getLastTabItem" , "setLastTabItem" , true ))list.put(prog.kw_length , execLib("cr_descr", "Editor" , "getLength" , null , true ))
@@ -819,17 +826,21 @@
list.put(prog.kw_merge_rc, execLib("cr_descr", "Buffer" , "mergeRowChanges" , null , true ))list.put(prog.kw_min_btn , execLib("cr_descr", "Window" , "isMinButton" , "setMinButton" , true ))list.put(prog.kw_min_val , execLib("cr_descr", "Slider" , "getMinValue" , "setMinValue" , true ))
+ list.put(prog.kw_min_schm, execLib("cr_descr", "TempTable" , "isMinSchemaMarshal" , "setMinSchemaMarshal" , true ))list.put(prog.kw_mnemon , execLib("cr_descr", "Mnemonic" , "getMnemonic" , null , true ))list.put(prog.kw_modified, execLib("cr_descr", "Widget" , "isModified" , "setModified" , true ))list.put(prog.kw_mou_ptr , execLib("cr_descr", "Widget" , "getMousePointer" , null , true ))list.put(prog.kw_mov_2eof, execLib("cr_descr", "Editor" , "moveCaretToEof" , null , true ))list.put(prog.kw_movable , execLib("cr_descr", "Widget" , "isMovable" , "setMovable" , true ))list.put(prog.kw_multiple, execLib("cr_descr", "Multiple" , "isMultiple" , "setMultiple" , true ))
+ list.put(prog.kw_namesp_p, execLib("cr_descr", "NamespaceURI" , "namespacePrefix" , "namespacePrefix" , true ))
+ list.put(prog.kw_namesp_u, execLib("cr_descr", "NamespaceURI" , "namespaceURI" , "namespaceURI" , true ))list.put(prog.kw_nested , execLib("cr_descr", "DataRelation" , "isNested" , "setNested" , true ))list.put(prog.kw_next_rid, execLib("cr_descr", "DataSource" , "getNextRowid" , "setNextRowid" , true ))list.put(prog.kw_no_cur_v, execLib("cr_descr", "Slider" , "isNoCurrentValue" , "setNoCurrentValue" , true ))list.put(prog.kw_no_em_sp, execLib("cr_descr", "Browse" , "isNoEmptySpace" , "setNoEmptySpace" , true ))list.put(prog.kw_no_focus, execLib("cr_descr", "Button" , "getNoFocus" , "setNoFocus" , true ))
+ list.put(prog.kw_no_sch_m, execLib("cr_descr", "TempTable" , "isNoSchemaMarshal" , "setNoSchemaMarshal" , true ))list.put(prog.kw_no_valid, execLib("cr_descr", "Browse" , "isNoValidate" , "setNoValidate" , true ))list.put(prog.kw_nodes , execLib("cr_descr", "Nodes" , "getNodes" , null , true ))list.put(prog.kw_num_buff, execLib("cr_descr", "BufferCollection" , "numBuffers" , null , true ))
@@ -907,6 +918,7 @@
list.put(prog.kw_save_fil, execLib("cr_descr", "Editor" , "saveFile" , null , true ))list.put(prog.kw_save_rch, execLib("cr_descr", "Buffer" , "saveRowChanges" , null , true ))list.put(prog.kw_save_wst, execLib("cr_descr", "DataSource" , "getSaveWhereString" , "setSaveWhereString" , true ))
+ list.put(prog.kw_sch_mars, execLib("cr_descr", "TempTable" , "getSchemaMarshal" , "setSchemaMarshal" , true ))list.put(prog.kw_sing_run, execLib("cr_descr", "Procedure" , "isSingleRun" , null , true ))list.put(prog.kw_singlton, execLib("cr_descr", "Procedure" , "isSingleton" , null , true ))list.put(prog.kw_scr_2cr , execLib("cr_descr", "Browse" , "scrollToCurrentRow" , null , true ))
@@ -922,7 +934,7 @@
list.put(prog.kw_selectbl, execLib("cr_descr", "Widget" , "isSelectable" , "setSelectable" , true ))list.put(prog.kw_selected, execLib("cr_descr", "Widget" , "isSelected" , "setSelected" , true ))list.put(prog.kw_sep_fgc , execLib("cr_descr", "Browse" , "getSeparatorFgColor" , "setSeparatorFgColor" , true ))
- list.put(prog.kw_serialzh, execLib("cr_descr", "BufferField" , "getSerializeHidden" , "setSerializeHidden" , true ))
+ list.put(prog.kw_serialzh, execLib("cr_descr", "SerializeHiddenable" , "getSerializeHidden" , "setSerializeHidden" , true ))list.put(prog.kw_serialzn, execLib("cr_descr", "NamedSerializable" , "getSerializeName" , "setSerializeName" , true ))list.put(prog.kw_serialzr, execLib("cr_descr", "Buffer" , "serializeRow" , null , true ))list.put(prog.kw_server , execLib("cr_descr", "Remotable" , "getServerHandle" , "setServerHandle" , true ))
@@ -973,7 +985,7 @@
list.put(prog.kw_xml_dtyp, execLib("cr_descr", "XmlNode" , "getXmlDataType" , "setXmlDataType" , true ))list.put(prog.kw_xml_nnam, execLib("cr_descr", "XmlNode" , "getXmlNodeName" , "setXmlNodeName" , true ))list.put(prog.kw_xml_ntyp, execLib("cr_descr", "XmlNode" , "getXmlNodeType" , "setXmlNodeType" , true ))
-
+
list.put(prog.kw_add_bcca, execLib("cr_descr", "EmailSender" , "addBccAddress" , "" , true ))list.put(prog.kw_add_c_n , execLib("cr_descr", "TreeNodeCollection" , "addChildNode" , null , true ))
@@ -1018,7 +1030,7 @@
list.put(prog.kw_caltitfg, execLib("cr_descr", "Calendar" , "getTitleForeColor" , "setTitleForeColor" , true ))list.put(prog.kw_caltrlfg, execLib("cr_descr", "Calendar" , "getTrailingForeColor" , "setTrailingForeColor" , true ))list.put(prog.kw_calupdwn, execLib("cr_descr", "Calendar" , "isUpDown" , "setUpDown" , true ))
- list.put(prog.kw_calvalue, execLib("cr_descr", "Calendar" , "getDateTimeValue" , "setDateTimeValue" , true ))
+ list.put(prog.kw_calvalue, execLib("cr_descr", "Calendar" , "getCalendarValue" , "setCalendarValue" , true ))list.put(prog.kw_cease , execLib("cr_descr", "FWDTimer" , "cease" , null , true ))list.put(prog.kw_clea_tab, execLib("cr_descr", "Signature" , "clearTablet" , "" , true ))list.put(prog.kw_clea_win, execLib("cr_descr", "Signature" , "clearSignatureWindow" , "" , true ))
@@ -1619,10 +1631,10 @@
methodText = "getResourceType"
- ftype == prog.kw_error
+ ftype == prog.kw_error and ref.type == prog.sys_handle
hwrap = "Error"methodText = "isError"
-
+
isAssign
methodText = "setError"
@@ -3894,12 +3906,8 @@
-
ftype == prog.kw_max_h_c
- htype == prog.kw_browse
- hwrap = "Browse"
- hwrap = "Window"
-
+ hwrap = "Window"methodText = "getMaxHeightChars"isAssign
methodText = "setMaxHeightChars"
@@ -3948,10 +3956,7 @@
ftype == prog.kw_min_h_c
- htype == prog.kw_browse
- hwrap = "Browse"
- hwrap = "Window"
-
+ hwrap = "MinHeightChars"methodText = "getMinHeightChars"isAssign
methodText = "setMinHeightChars"
@@ -4036,23 +4041,6 @@
methodText = "name"
-
-
- ftype == prog.kw_namesp_p
- hwrap = "XEntity"
- methodText = "getNamespacePrefix"
- isAssign
- methodText = "setNamespacePrefix"
-
-
-
- ftype == prog.kw_namesp_u
- hwrap = "NamespaceURI"
- methodText = "getNamespaceURI"
- isAssign
- methodText = "setNamespaceURI"
-
- ftype == prog.kw_new
methodText = "newlyCreated"
=== modified file 'rules/convert/variable_references.rules'
--- rules/convert/variable_references.rules 2020-10-09 09:56:27 +0000
+++ rules/convert/variable_references.rules 2020-11-25 18:40:50 +0000
@@ -144,6 +144,7 @@
** SBI 20200505 Added COM-SELF conversion support.
** RFB 20201002 Added additional parameter to thisProcedure when an annotation is found. Ref #4861
** SVL 20201009 Emit extent fields where subscript is an expression.
+** GES 20201125 Added OS-USERID as a FWD-specific 4GL extension.
*/
-->
@@ -789,6 +790,9 @@
oldtype == prog.kw_os_err
methodTxt = "FileSystemOps.getLastError"
+ oldtype == prog.kw_os_uid
+ methodTxt = "SecurityOps.getOSUserId"
+ oldtype == prog.kw_pro_arch
methodTxt = "EnvironmentOps.getProcessArchitecture"
=== modified file 'rules/fixups/functions_procedures.rules'
--- rules/fixups/functions_procedures.rules 2020-05-04 12:50:30 +0000
+++ rules/fixups/functions_procedures.rules 2020-12-19 00:59:30 +0000
@@ -7,7 +7,7 @@
**
** Copyright (c) 2013-2020 Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ _________________________________Description_________________________________
+** _#_ _I_ __Date__ _______________________________________Description_______________________________________
** 001 CA 20130118 Added support for persistent procedures, procedure
** handles and super procedures. DYNAMIC-FUNCTION(expr IN
** handle) where expr is not a constant has the IN handle
@@ -42,7 +42,8 @@
** RFB 20200429 Removed a dumpTree that wasn't providing any useful information
** after the "WARNING: Hiding duplicate...".
** RFB 20200504 Shortened up some other log messages to still provide info,
-** but not perform dumpTree().
+** but not perform dumpTree().
+** 012 OM 20201203 Fixed handling of READ-ONLY attributes.
*/
-->
@@ -180,57 +181,40 @@
funcRetTypes.put(fname, cls)
-
-
+
+
type == prog.assign
ref = copy.getFirstChild()ref.type == prog.colon
-
-
+
ref = ref.getChildAt(1)
-
+
ref.type == prog.db_ref_non_static
ref = ref.nextSibling
-
+
evalLib("read_only_attribute", ref)
-
- printfln("READ_ONLY_WARNING: REWRITING attribute assignment as string at %s line %05d column %05d\n%s\n",
+ printfln("READ_ONLY_WARNING: REWRITING attribute assignment as string at %s line %05d column %05d",
file,
line,
- this.getColumn(),
- this.dumpTree())
+ this.getColumn())
-
- pref = copy.getChildAt(1)
- pref.type = prog.expression
-
-
- pref = pref.getChildAt(0)
-
- pref.text = sprintf('"%s"', ref.text)
-
- pref.type = prog.string
+
+ pref = createProgressAst(prog.expression, copy, 1)
+ pref.putAnnotation("read-only attribute helper", true)
+ pref = createProgressAst(prog.string, pref)
+ pref.text = sprintf('"%s"', ref.text)pref.putAnnotation("no_wrap", true)
-
-
- pref.firstChild != null
- ref = pref.firstChild
- ref.remove()
-
+
-
+
=== modified file 'rules/gaps/expressions.rules'
--- rules/gaps/expressions.rules 2020-09-10 09:54:53 +0000
+++ rules/gaps/expressions.rules 2021-01-13 21:04:41 +0000
@@ -185,7 +185,9 @@
** SBI 20200520 LOAD-PICTURE is fully supported.
** 053 GES 20200628 Added SET-LASTKEY method and updated DEFAULT-STRING and DEFAULT-VALUE attributes.
** HC 20200726 Initial implementation of SPREADSHEET widget and related changes.
-** 054 CA 20200910 Added BUFFER:SERIALIZE-ROW() method conversion as FULL.
+** CA 20200910 Added BUFFER:SERIALIZE-ROW() method conversion as FULL.
+** GES 20201125 Added OS-USERID as a FWD-specific 4GL extension.
+** OM 20201120 Updated gaps for multiple methods/attributes related to datasets and xml serialization.
*/
-->
@@ -377,7 +379,7 @@
funcs.put(prog.kw_cur_res , rw.cvt_lvl_full | rw.rt_lvl_full)funcs.put(prog.kw_cur_val , rw.cvt_lvl_full | rw.rt_lvl_full)funcs.put(prog.kw_cvt_dt , rw.cvt_lvl_none | rw.rt_lvl_none)
- funcs.put(prog.kw_data_sm , rw.cvt_lvl_full | rw.rt_lvl_full)
+ funcs.put(prog.kw_data_sm , rw.cvt_lvl_full | rw.rt_lvl_full)funcs.put(prog.kw_date , rw.cvt_lvl_full | rw.rt_lvl_full)funcs.put(prog.kw_date_tz , rw.cvt_lvl_full | rw.rt_lvl_full)funcs.put(prog.kw_datetime, rw.cvt_lvl_full | rw.rt_lvl_full)
@@ -600,6 +602,7 @@
vars.put(prog.kw_opsys , rw.cvt_lvl_full | rw.rt_lvl_full)vars.put(prog.kw_os_drv , rw.cvt_lvl_full | rw.rt_lvl_full)vars.put(prog.kw_os_err , rw.cvt_lvl_full | rw.rt_lvl_full)
+ vars.put(prog.kw_os_uid , rw.cvt_lvl_full | rw.rt_lvl_full)vars.put(prog.kw_pro_arch, rw.cvt_lvl_full | rw.rt_lvl_full)vars.put(prog.kw_proc_hnd, rw.cvt_lvl_none | rw.rt_lvl_none)vars.put(prog.kw_proc_st , rw.cvt_lvl_none | rw.rt_lvl_none)
@@ -819,7 +822,7 @@
attrs.put(prog.kw_data_e_r, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_data_scm, rw.cvt_lvl_full | rw.rt_lvl_stub)attrs.put(prog.kw_dataset , rw.cvt_lvl_full | rw.rt_lvl_full)
- attrs.put(prog.kw_data_sm , rw.cvt_lvl_full | rw.rt_lvl_partial)
+ attrs.put(prog.kw_data_sm , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_data_src, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_data_sri, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_datatype, rw.cvt_lvl_full | rw.rt_lvl_full)
@@ -931,8 +934,8 @@
attrs.put(prog.kw_fil_c_t , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_fill , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_filled , rw.cvt_lvl_full | rw.rt_lvl_full)
- attrs.put(prog.kw_fill_mod, rw.cvt_lvl_full | rw.rt_lvl_full_restr)
- attrs.put(prog.kw_fill_wst, rw.cvt_lvl_full | rw.rt_lvl_full)
+ attrs.put(prog.kw_fill_mod, rw.cvt_lvl_full | rw.rt_lvl_full)
+ attrs.put(prog.kw_fill_wst, rw.cvt_lvl_full | rw.rt_lvl_partial)attrs.put(prog.kw_fil_m_d , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_fil_m_t , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_fil_name, rw.cvt_lvl_full | rw.rt_lvl_full)
@@ -1143,7 +1146,7 @@
attrs.put(prog.kw_language, rw.cvt_lvl_none | rw.rt_lvl_none)attrs.put(prog.kw_large , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_last_ar , rw.cvt_lvl_full | rw.rt_lvl_full)
- attrs.put(prog.kw_last_bat, rw.cvt_lvl_full | rw.rt_lvl_stub)
+ attrs.put(prog.kw_last_bat, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_last_ch , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_last_frm, rw.cvt_lvl_none | rw.rt_lvl_none)attrs.put(prog.kw_last_obj, rw.cvt_lvl_full | rw.rt_lvl_full)
@@ -1217,7 +1220,7 @@
attrs.put(prog.kw_menu_mou, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_merge_bf, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_merge_ch, rw.cvt_lvl_full | rw.rt_lvl_full)
- attrs.put(prog.kw_merge_rc, rw.cvt_lvl_full | rw.rt_lvl_stub)
+ attrs.put(prog.kw_merge_rc, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_min_btn , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_min_cwch, rw.cvt_lvl_none | rw.rt_lvl_none)attrs.put(prog.kw_min_cwpx, rw.cvt_lvl_none | rw.rt_lvl_none)
@@ -1264,6 +1267,7 @@
attrs.put(prog.kw_nodv_2lc, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_no_em_sp, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_no_focus, rw.cvt_lvl_full | rw.rt_lvl_full)
+ attrs.put(prog.kw_no_pre, rw.cvt_lvl_full | rw.rt_lvl_full_restr)attrs.put(prog.kw_normalze, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_no_sch_m, rw.cvt_lvl_none | rw.rt_lvl_none)attrs.put(prog.kw_no_valid, rw.cvt_lvl_full | rw.rt_lvl_full)
@@ -1366,8 +1370,8 @@
attrs.put(prog.kw_read_fil, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_read_jsn, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_read_onl, rw.cvt_lvl_full | rw.rt_lvl_full)
- attrs.put(prog.kw_read_xml, rw.cvt_lvl_full | rw.rt_lvl_partial)
- attrs.put(prog.kw_read_xsc, rw.cvt_lvl_full | rw.rt_lvl_stub)
+ attrs.put(prog.kw_read_xml, rw.cvt_lvl_full | rw.rt_lvl_partial)
+ attrs.put(prog.kw_read_xsc, rw.cvt_lvl_full | rw.rt_lvl_partial)attrs.put(prog.kw_recid , rw.cvt_lvl_full | rw.rt_lvl_full_restr)attrs.put(prog.kw_rec_len , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_recurse , rw.cvt_lvl_full | rw.rt_lvl_stub)
@@ -1392,7 +1396,7 @@
attrs.put(prog.kw_repos_2i, rw.cvt_lvl_full_restr | rw.rt_lvl_full_restr)attrs.put(prog.kw_repos_2r, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_repos_b , rw.cvt_lvl_full | rw.rt_lvl_basic)
- attrs.put(prog.kw_repos , rw.cvt_lvl_full | rw.rt_lvl_partial)
+ attrs.put(prog.kw_repos , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_repos_f , rw.cvt_lvl_full | rw.rt_lvl_basic)attrs.put(prog.kw_rep_stxt, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_req_info, rw.cvt_lvl_none | rw.rt_lvl_full)
@@ -1449,7 +1453,7 @@
attrs.put(prog.kw_seal , rw.cvt_lvl_full | rw.rt_lvl_partial)attrs.put(prog.kw_seal_tst, rw.cvt_lvl_full | rw.rt_lvl_stub)attrs.put(prog.kw_search , rw.cvt_lvl_full | rw.rt_lvl_full_restr)
- attrs.put(prog.kw_sel_all , rw.cvt_lvl_full | rw.rt_lvl_full)
+ attrs.put(prog.kw_sel_all , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_selectbl, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_selected, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_sel_end , rw.cvt_lvl_full | rw.rt_lvl_full)
@@ -1463,7 +1467,7 @@
attrs.put(prog.kw_sep_fgc , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_seps , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_serialzh, rw.cvt_lvl_full | rw.rt_lvl_partial)
- attrs.put(prog.kw_serialzn, rw.cvt_lvl_full | rw.rt_lvl_partial)
+ attrs.put(prog.kw_serialzn, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_serialzr, rw.cvt_lvl_full | rw.rt_lvl_stub)attrs.put(prog.kw_server , rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_sess_end, rw.cvt_lvl_none | rw.rt_lvl_none)
@@ -1475,8 +1479,8 @@
attrs.put(prog.kw_set_blue, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_set_brk , rw.cvt_lvl_full | rw.rt_lvl_stub)attrs.put(prog.kw_set_buf , rw.cvt_lvl_full | rw.rt_lvl_full)
- attrs.put(prog.kw_set_cbac, rw.cvt_lvl_full | rw.rt_lvl_partial)
- attrs.put(prog.kw_set_cb_p, rw.cvt_lvl_full | rw.rt_lvl_partial)
+ attrs.put(prog.kw_set_cbac, rw.cvt_lvl_full | rw.rt_lvl_full)
+ attrs.put(prog.kw_set_cb_p, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_set_clnt, rw.cvt_lvl_full | rw.rt_lvl_basic)attrs.put(prog.kw_set_comm, rw.cvt_lvl_full | rw.rt_lvl_full)attrs.put(prog.kw_set_c_p , rw.cvt_lvl_full | rw.rt_lvl_full)
=== modified file 'rules/gaps/lang_stmts.rules'
--- rules/gaps/lang_stmts.rules 2020-03-31 19:34:28 +0000
+++ rules/gaps/lang_stmts.rules 2020-12-16 15:49:52 +0000
@@ -36,6 +36,7 @@
** GES 20191015 More updates for OO and structured error handling.
** 022 HC 20200331 Implemented support for OPEN-POPUP legacy method, which opens popup menu set
** on the widget.
+** 023 EVL 20201216 Added function to interrupt metafile recording.
*/
-->
@@ -261,6 +262,7 @@
opts.put(prog.kw_mf_gxy, rw.cvt_lvl_partial | rw.rt_lvl_basic)opts.put(prog.kw_mf_gzf, rw.cvt_lvl_partial | rw.rt_lvl_basic)opts.put(prog.kw_mf_init, rw.cvt_lvl_partial | rw.rt_lvl_basic)
+ opts.put(prog.kw_mf_ir, rw.cvt_lvl_partial | rw.rt_lvl_basic)opts.put(prog.kw_mf_mpdf, rw.cvt_lvl_partial | rw.rt_lvl_basic)opts.put(prog.kw_mf_p2mu, rw.cvt_lvl_partial | rw.rt_lvl_basic)opts.put(prog.kw_mf_rp, rw.cvt_lvl_partial | rw.rt_lvl_basic)
=== modified file 'rules/include/common-progress.rules'
--- rules/include/common-progress.rules 2020-09-30 11:51:44 +0000
+++ rules/include/common-progress.rules 2021-01-13 21:04:41 +0000
@@ -6,7 +6,7 @@
**
** Copyright (c) 2005-2020, Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ __JPRM__ ____________________________Description_____________________________
+** _#_ _I_ __Date__ __JPRM__ __________________________________Description__________________________________
** 001 GES 20050721 @21761 Good working version which provides many callable functions/rules
** that allow the centralized definition and reuse of complex
** expressions, logic and processing.
@@ -440,6 +440,7 @@
** GES 20200521 Added legacy name overrides.
** 236 CA 20200803 Added create_temp_table_key_criteria.
** 237 IAS 20200809 Added support for the MAX-WIDTH FIELD attribute.
+** OM 20201120 The DECIMALS attribute is writable for TEMP-TABLES but not for permanent tables.
*/
-->
@@ -6881,39 +6882,38 @@
-
+
-
- ftype = #(int) (#(long) ref.getAnnotation("oldtype"))
-
+ ftype = #(int) (#(long) ref.getAnnotation("oldtype"))
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -6998,7 +6998,6 @@
ftype == prog.kw_dde_id ||
ftype == prog.kw_dde_name ||
ftype == prog.kw_dde_topi ||
- ftype == prog.kw_decimals ||
ftype == prog.kw_def_bufh ||
ftype == prog.kw_def_val ||
ftype == prog.kw_disp_typ ||
=== modified file 'rules/schema/dmo_common.rules'
--- rules/schema/dmo_common.rules 2020-10-22 11:53:30 +0000
+++ rules/schema/dmo_common.rules 2021-01-31 10:14:52 +0000
@@ -124,15 +124,20 @@
** SBI 20190728 Added "help" annotation.
** 056 OM 20190831 Added hidden fields and index specific to BEFORE TEMP-TABLES.
** 057 IAS 20200601 Added support for the table/field string attributes (*-SA properties).
-** 058 OM 20200108 Moved back the extent support method definition to main interface.
+** 058 IAS 20200604 Added support for the table/filed 'can*' attributes and table 'valexp' attr
+** 059 OM 20200108 Moved back the extent support method definition to main interface.
** Added support for "dirty-read" hint/annotation.
** Removed generation of DMO implementation classes.
-** 059 IAS 20200604 Added support for the table/filed 'can*' attributes and table 'valexp' attributes
+** 060 IAS 20200604 Added support for the table/filed 'can*' attributes and table 'valexp' attributes
** IAS 20200624 Added support for the field VIEW-AS attributes
** IAS 20200809 Added support for the field DECIMALS, DESCRIPTION, CASE-SENSITIVE, and
** MAX-WIDTH attributes
-** 060 OM 20200925 Fixed latent issue. adjustDataType() returned null in some cases.
+** OM 20200925 Fixed latent issue. adjustDataType() returned null in some cases.
** CA 20201021 Fixed runtime conversion of dynamic temp-tables with Progress.Lang.Object fields.
+** SVL 20201111 Raise error if LABEL/COLUMN-LABEL matches reserved FWD 'null string'.
+** OM 20201120 Added "no-undo" annotation.
+** IAS 20201203 Added generation database object for the word tables' support.
+** IAS 20201219 Added 'CPSTREAM' import task argument
-->
+ copy.isAnnotation("no-undo-tt")
+ tpl.graft("annotation_assign_true", null, tableAst, "key", "noUndo")
+
+
copy.isAnnotation("serialize-name")
@@ -951,15 +961,13 @@
- !isWord
- idxName = idxAst.getAnnotation("historical")
-
- !idxAst.parent.isAnnotation("drop")
-
- ddl.addIndex(
- ddlSchema, legacyTable, idxName, idxAst.text, isPrimary, isUnique, isWord)
-
-
+ idxName = idxAst.getAnnotation("historical")
+
+ !idxAst.parent.isAnnotation("drop")
+
+ ddl.addIndex(
+ ddlSchema, legacyTable, idxName, idxAst.text, isPrimary, isUnique, isWord)
+
@@ -971,8 +979,9 @@
idxCompAst != null
ref = getAst(#(long) idxCompAst.getAnnotation("refid"))
-
-
+
+
+
isDesc = idxCompAst.isAnnotation("descend") and
#(boolean) idxCompAst.getAnnotation("descend")
@@ -985,17 +994,15 @@
'legacy_name', legacyName)
!ref.parent.isAnnotation("drop")
- !isWord
-
- ddl.addIndexComponent(ddlSchema, legacyTable, idxName, ref.text,
- #(java.lang.String) ref.getAnnotation("column"),
- legacyName,
- ref.getAnnotation("datatype").equals("character"),
- ref.isAnnotation("case-sensitive") and
- #(boolean) ref.getAnnotation("case-sensitive"),
- isDesc)
-
-
+
+ ddl.addIndexComponent(ddlSchema, legacyTable, idxName, ref.text,
+ #(java.lang.String) ref.getAnnotation("column"),
+ legacyName,
+ ref.getAnnotation("datatype").equals("character"),
+ ref.isAnnotation("case-sensitive") and
+ #(boolean) ref.getAnnotation("case-sensitive"),
+ isDesc)
+ isDesc
@@ -1009,6 +1016,17 @@
idxCompAst = idxAst.getImmediateChild(data.index_col, idxCompAst)
+
+ isWord
+
+ wordTableName = ddl.getWordTableName(ddlSchema, legacyTable, idxName)
+
+
+ tpl.graft('annotation_assign_string', null, primaryAst,
+ 'key', 'wordtablename',
+ 'value', wordTableName)
+
+ idxAst = copy.getImmediateChild(data.index, idxAst)
@@ -1579,7 +1597,7 @@
-
+
newDMO
@@ -1690,6 +1708,7 @@
sourceAst.isAnnotation("col_lab")
tmpString = sourceAst.getAnnotation('col_lab')
+ execLib("checkReservedNull", tmpString, "COLUMN-LABEL")
tpl.graft('annotation_assign_string', null, fieldAst,
'key', 'columnLabel',
@@ -1720,6 +1739,7 @@
sourceAst.isAnnotation("label")
tmpString = sourceAst.getAnnotation('label')
+ execLib("checkReservedNull", tmpString, "LABEL")
tpl.graft('annotation_assign_string', null, fieldAst,
'key', 'label',
@@ -2452,7 +2472,7 @@
hiberProps = execLib("buildDbImportProperties",
- dbName, targetDb, null, null, null)
+ dbName, targetDb, null, null, null, null)
config.addProperties(hiberProps)
@@ -2463,6 +2483,18 @@
p2jDatabase = create("com.goldencode.p2j.persist.Database", dbName)
+
+
+
+
+
+
+ param.equals("__NULL_STRING__#!^+*__")
+ throwException(sprintf("ERROR: By miraculous coincidence in-app %s
+ matches reserved FWD 'null string' constant '__NULL_STRING__#!^+*__'. Update
+ this constant in FWD.", paramType))
+
+
=== modified file 'rules/schema/generate_ddl.xml'
--- rules/schema/generate_ddl.xml 2020-09-23 23:47:02 +0000
+++ rules/schema/generate_ddl.xml 2021-01-13 21:04:41 +0000
@@ -5,7 +5,7 @@
**
** Copyright (c) 2006-2020, Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ _________________________________Description_________________________________
+** _#_ _I_ __Date__ _______________________________________Description_______________________________________
** 001 GES 20100629 Created initial version based on reindex.xml. Walks P2O
** AST to setup Hibernate, then uses Hibernate to generate
** DDL for all tables and for all indexes.
@@ -35,6 +35,7 @@
** 015 VMN 20140502 Added enhance schema name conversion support for hint "escape" attribute.
** 016 OM 20160712 Removed p2j_id_generator_sequence from generated index DDL.
** 017 OM 20200906 New ORM implementation.
+** 018 OM 20201231 The index components is aware of all their name (legacy/orm/sql).
-->
-
@@ -119,13 +119,12 @@
- type == data.property and this.isAnnotation('composite')
+ type == data.property and this.isAnnotation("extent")
dataType = execLib('getDataType', this)dType = execLib('adjustDataType', dataType)
- refAst = getAst(#(long) this.getAnnotation('composite'))
- extentVal = refAst.getAnnotation('extent')
+ extentVal = this.getAnnotation("extent")descList = execLib('gatherDocData', this)
=== modified file 'rules/schema/import.xml'
--- rules/schema/import.xml 2020-09-23 23:47:02 +0000
+++ rules/schema/import.xml 2021-01-25 07:55:58 +0000
@@ -97,6 +97,8 @@
** 030 OM 20200906 New ORM implementation.
** 031 CA 20200910 Fixed the datetime(-tz) field's initial value at import - it uses ISO8601
** format or NOW function.
+** IAS 20201219 Added 'CPSTREAM' import task argument
+** Added word tables population on import
-->
-
-
-
-
-
+
+
+
+
+
+
@@ -279,7 +282,7 @@
printfln('INFO: Using %d threads for import', maxThreads)cacheables = create('java.util.HashSet')
- ormProps = execLib("buildDbImportProperties", dbName, targetDb, url, uid, pw)
+ ormProps = execLib("buildDbImportProperties", dbName, targetDb, url, uid, pw, cpstream)fwdDialect = create(ormProps.getProperty("dialect"))
@@ -363,6 +366,7 @@
+ schemaName = text
@@ -441,6 +445,14 @@
type == data.index and !skipImport
+
+ wordTableName = null
+
+ this.isAnnotation("word")
+
+ wordTableName = imp.getWordTableName(dmoIface, text)
+
+
indexDef = create("com.goldencode.p2j.persist.P2JIndex",
@@ -448,7 +460,8 @@
data.formatWithEscapedParameter("idx__%s", text),
this.isAnnotation("unique"),
this.isAnnotation("primary"),
- this.isAnnotation("word"))
+ this.isAnnotation("word"),
+ wordTableName)
=== modified file 'rules/schema/import_util.rules'
--- rules/schema/import_util.rules 2020-07-13 06:22:42 +0000
+++ rules/schema/import_util.rules 2020-12-23 16:58:23 +0000
@@ -19,6 +19,7 @@
** 007 VMN 20131106 Removed "sqlserver2008" target DB because SQL Server 2008 does not
** support sequences.
** 008 OM 20200408 Replaced Hibernate with customized orm.
+** 009 IAS 20201219 Added 'CPSTREAM' import task argument
-->
+
+
+
@@ -159,6 +163,10 @@
pw = "p3rs1st"
+ cpstream != null
+ >dbImportParams.put("dump.cpstream", cpstream)
+
+
dbImportParams.put("dialect", dialectClass)dbImportParams.put("database_name", dbName)dbImportParams.put("connection.driver_class", driverClass)
=== modified file 'rules/schema/index_test.xml'
--- rules/schema/index_test.xml 2020-09-23 23:47:02 +0000
+++ rules/schema/index_test.xml 2021-01-13 21:04:41 +0000
@@ -185,7 +185,8 @@
!indexDef.isUnique()
indexDef.addComponent(
- this.getImmediateChild(data.primary, null).getAnnotation('column').toString())
+ this.getImmediateChild(data.primary, null)
+ .getAnnotation('column').toString())
=== modified file 'rules/schema/java_dmo.xml'
--- rules/schema/java_dmo.xml 2020-10-22 11:53:30 +0000
+++ rules/schema/java_dmo.xml 2021-01-31 10:14:52 +0000
@@ -35,6 +35,7 @@
** Dropped support for generation of DMO Impls.
** OM 20200610 Generated the _Sequences enum.
** 016 CA 20201021 Fixed runtime conversion of dynamic temp-tables with Progress.Lang.Object fields.
+** IAS 20201203 Added generation database object for the word tables' support.
-->
ddl.generateIndexDDLs(ddlSchema)
+
+ ddl.generateWordTablesDDLs(ddlSchema)
=== modified file 'rules/schema/metaschema.xml'
--- rules/schema/metaschema.xml 2020-09-07 16:23:31 +0000
+++ rules/schema/metaschema.xml 2021-01-13 21:04:41 +0000
@@ -5,7 +5,7 @@
**
** Copyright (c) 2013-2020, Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ _________________________________Description__________________________________
+** _#_ _I_ __Date__ _______________________________________Description_______________________________________
** 001 ECF 20130515 Created initial version.
** 002 ECF 20131028 Exported more fields.
** 003 VMN 20140202 Added support for shift of order on custom denormalization of fields with
@@ -28,6 +28,7 @@
** 015 ECF 20200906 New ORM implementation.
** 016 IAS 20200624 Added support for the field VIEW-AS attributes
** IAS 20200809 Added support for the field DECIMALS, DESCRIPTION, CASE-SENSITIVE and MAX-WIDTH attributes
+** OM 20201120 Dropped deprecated 'newName' pseudo-field/property.
-->
- execLib("addField", "newName", text, "c")
-
@@ -477,7 +475,6 @@
newName = textnewName = getNoteString("denormalizedpropertyname")
- execLib("addField", "newName", newName, "c")execLib("addField", "dataType", getNoteString("datatype"), "c")
=== modified file 'rules/schema/p2o.xml'
--- rules/schema/p2o.xml 2020-10-06 07:23:27 +0000
+++ rules/schema/p2o.xml 2021-01-13 21:04:41 +0000
@@ -267,10 +267,11 @@
** this was fixed so that the .schema AST is a real .schema, and not .p2o.
** OM 20200925 Added support for generating denormalized extent properties in dynamic
** conversion of temp-tables.
-** 098 CA 20201005 Fixed incremental conversion for temp-tables - nextTTSuffix must be inherited from
-** the previous, existing, peer, when a file adds usage for a temp-table already
-** defined in another file.
-** Fixed nextTTSuffix for SHARED TEMP-TABLE without a NEW SHARED.
+** CA 20201005 Fixed incremental conversion for temp-tables - nextTTSuffix must be inherited
+** from the previous, existing, peer, when a file adds usage for a temp-table
+** already defined in another file.
+** Fixed nextTTSuffix for SHARED TEMP-TABLE without a NEW SHARED.
+** OM 20201120 Added NO-UNDO attribute / temp-table option support.
-->
tmpTabNodes.put(tmpTabKey, peer)
=== modified file 'rules/schema/p2o_post.xml'
--- rules/schema/p2o_post.xml 2020-09-23 23:47:02 +0000
+++ rules/schema/p2o_post.xml 2021-01-13 21:04:41 +0000
@@ -5,12 +5,13 @@
**
** Copyright (c) 2005-2020, Golden Code Development Corporation.
**
-** _#_ _I_ __Date__ _________________________________Description__________________________________
+** _#_ _I_ __Date__ _______________________________________Description_______________________________________
** 001 ECF 20150326 Refactored later rule-sets from p2o.xml to here. This is necessary because
** we are dependent upon peer nodes being available for cross-schema joins.
** 002 AIL 20190722 Added rule-sets for triggers.
** 003 OM 20190826 The BEFORE-BUFFERs do not inherit the indexes of their parent AFTER-BUFFER.
** 004 ECF 20200601 Fixed typo.
+** 005 OM 20201231 The index components is aware of all their name (legacy/orm/sql).
-->
+
desc}, {@code desc-->asc});
+ * else {@code false}.
* @param sortCrit
- * List of SortCriterion objects appropriate to the
- * database dialect.
+ * List of {@code SortCriterion} objects appropriate to the database dialect.
*
* @return An HQL compliant order by clause.
*/
- private String buildSortClause(boolean invert,
- List sortCrit)
+ private String buildSortClause(boolean invert, List sortCrit)
{
StringBuilder buf = new StringBuilder("order by ");
- // Inject multiplex ID for temp table before primary key, so as to
- // match the way these components are ordered in the table's index.
+ // inject multiplex ID for temp table before primary key, so as to match the way these components are
+ // ordered in the table's index.
if (multiplexSort)
{
injectMultiplexSort(invert, buf);
buf.append(", ");
}
-
+
int i = 0;
Iterator iter = sortCrit.iterator();
for (; iter.hasNext(); i++)
@@ -1020,7 +1012,7 @@
buf.append(next.toSortExpression(invert));
}
-
+
return buf.toString();
}
=== modified file 'src/com/goldencode/p2j/persist/HQLPreprocessor.java'
--- src/com/goldencode/p2j/persist/HQLPreprocessor.java 2020-10-01 17:13:26 +0000
+++ src/com/goldencode/p2j/persist/HQLPreprocessor.java 2020-11-20 18:40:59 +0000
@@ -6059,7 +6059,7 @@
}
}
- /** A public container for storing a pair of properties. It is immutable, used for transfering data. */
+ /** A public container for storing a pair of properties. It is immutable, used for transferring data. */
public static final class PropertyPair
{
/** Operator used for joining the properties. */
=== modified file 'src/com/goldencode/p2j/persist/IndexHelper.java'
--- src/com/goldencode/p2j/persist/IndexHelper.java 2020-09-23 23:47:02 +0000
+++ src/com/goldencode/p2j/persist/IndexHelper.java 2020-10-14 21:06:19 +0000
@@ -46,6 +46,8 @@
** 022 ECF 20200906 New ORM implementation.
** 023 OM 20200919 Improved access to dmo metadata.
** OM 20200924 P2JIndexComponent carries multiple information to avoid map lookups for them.
+** OM 20201008 Detected direction of used index and stored for further access.
+** OM 20201012 Force use locally cached meta information instead of map lookup.
*/
/*
@@ -109,6 +111,7 @@
import com.goldencode.p2j.persist.lock.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.util.*;
+import org.apache.commons.lang3.tuple.*;
/**
* Helper object to manage database index information. One instance of this
@@ -139,14 +142,17 @@
/** Logger */
private static final Logger LOG = LogHelper.getLogger(IndexHelper.class);
+ /** Is FINE level logging allowed? */
+ private static final boolean LOG_FINE = LOG.isLoggable(Level.FINE);
+
/** Cache of instances, mapped by database */
private static final Map cache = new HashMap<>();
/** Primary (non-dirty) database with which this helper is associated */
private final Database database;
- /** Map of sort clauses to index names */
- private final Map indexBySort = new HashMap<>();
+ /** Map of sort clauses to pair of index names and their direction ({@code false} means reversed). */
+ private final Map> indexBySort = new HashMap<>();
/** Map of property names to lists of indexes containing them */
private final Map> indexesByProperty = new HashMap<>();
@@ -161,8 +167,7 @@
private final Map primaryIndexByEntity = new HashMap<>();
/** Map of entity keys to maps of index names to index property names */
- private final Map>> indexMapsByEntity =
- new HashMap<>();
+ private final Map>> indexMapsByEntity = new HashMap<>();
/** Map of entity keys to indexed properties */
private final Map> propsByEntity = new HashMap<>();
@@ -287,18 +292,21 @@
String index = null;
String entity = buffer.getEntityName();
SortKey key = new SortKey(entity, sort);
+ Pair indexAndDir;
synchronized (indexBySort)
{
- index = indexBySort.get(key);
- if (index == null)
- {
- index = lookupIndexForSort(buffer, sort);
- }
- else if ("".equals(index))
- {
- index = null;
- }
+ indexAndDir = indexBySort.get(key);
+ if (indexAndDir == null)
+ {
+ indexAndDir = lookupIndexForSort(buffer, sort);
+ }
+ }
+
+ index = indexAndDir.getLeft();
+ if ("".equals(index))
+ {
+ index = null;
}
return index;
@@ -384,40 +392,61 @@
}
/**
- * Retrieve a {@link DMOSorter} instance which is able to sort DMO
- * instances according to the sort criteria specified by the given sort
- * phrase.
+ * Retrieve a {@link DMOSorter} instance which is able to sort DMO instances according to the sort criteria
+ * specified by the given sort phrase.
*
* @param buffer
- * Record buffer which contains information about the DMO entity
- * associated with the sort phrase.
+ * Record buffer which contains information about the DMO entity associated with the sort phrase.
* @param sort
- * A sort phrase which includes one or more DMO property
- * references, each qualified by a DMO alias and followed by a
- * keyword indicating sort direction.
+ * A sort phrase which includes one or more DMO property references, each qualified by a DMO alias
+ * and followed by a keyword indicating sort direction.
*
- * @return DMO sorter object which can sort DMOs according to the given
- * index.
+ * @return DMO sorter object which can sort DMOs according to the given index.
*
* @throws PersistenceException
- * if there is any error querying index metadata from the JDBC
- * driver of the associated database.
+ * if there is any error querying index metadata from the JDBC driver of the associated database.
*/
public DMOSorter getSorterForSortPhrase(RecordBuffer buffer, String sort)
throws PersistenceException
{
+ Pair indexAndDir;
SortKey key = new SortKey(buffer.getEntityName(), sort);
synchronized (indexBySort)
{
- String index = indexBySort.get(key);
- if (index == null)
+ indexAndDir = indexBySort.get(key);
+ if (indexAndDir == null)
{
- // This will add an appropriate sorter to the sorters map, if possible.
+ // this will add an appropriate sorter to the sorters map, if possible
lookupIndexForSort(buffer, sort);
}
-
- return sorters.get(key);
+ }
+
+ return sorters.get(key);
+ }
+
+ /**
+ * Obtain the direction of the index used to sort the buffer by a specified sort phrase.
+ *
+ * @param buffer
+ * Record buffer which contains information about the DMO entity associated with the sort phrase.
+ * @param sort
+ * A sort phrase which includes one or more DMO property references, each qualified by a DMO alias
+ * and followed by a keyword indicating sort direction.
+ *
+ * @return {@code true} if the direction is the same as the index, {@code false} if the direction is
+ * inversed (I.e. the result set is reversed compared to the index) and {@code null} if the sort
+ * phrase does not benefit from any index, or the sort phrase was not processed yet for this
+ * buffer.
+ */
+ public Boolean getIndexDirection(RecordBuffer buffer, String sort)
+ {
+ SortKey key = new SortKey(buffer.getEntityName(), sort);
+
+ synchronized (indexBySort)
+ {
+ Pair indexAndDir = indexBySort.get(key);
+ return indexAndDir == null ? null : indexAndDir.getRight();
}
}
@@ -651,7 +680,7 @@
list.trimToSize();
}
- if (LOG.isLoggable(Level.FINE))
+ if (LOG_FINE)
{
LOG.log(Level.FINE,
"Indexes " + (unique ? "[unique]" : "[all]") + " for entity '" + entity + "': " + list);
@@ -734,7 +763,7 @@
}
}
- if (LOG.isLoggable(Level.FINE))
+ if (LOG_FINE)
{
LOG.log(Level.FINE,
"Indexes " + (unique ? "[unique]" : "[non-unique]") +
@@ -796,7 +825,7 @@
list.trimToSize();
}
- if (LOG.isLoggable(Level.FINE))
+ if (LOG_FINE)
{
LOG.log(Level.FINE, "Indexes for '" + entity + ":" + property + "': " + list);
}
@@ -824,10 +853,13 @@
* if there is any error querying index metadata from the JDBC driver of the
* associated database.
*/
- private String lookupIndexForSort(RecordBuffer buffer, String sort)
+ private Pair lookupIndexForSort(RecordBuffer buffer, String sort)
throws PersistenceException
{
+ Pair ret = null;
String indexName = null;
+ Boolean direction = null;
+ int badDirection = -1;
SortKey key = new SortKey(buffer.getEntityName(), sort);
Iterator iter = buffer.dmoInfo.getDatabaseIndexes();
Class extends DataModelObject> dmoIface = buffer.dmoInfo.getAnnotatedInterface();
@@ -839,6 +871,9 @@
while (iter.hasNext())
{
boolean match = true;
+ direction = null;
+ int i = 0;
+ badDirection = -1;
P2JIndex index = iter.next();
Iterator comps = index.components(true);
Iterator crits = scList.iterator();
@@ -868,20 +903,46 @@
match = false;
break;
}
+
+ i++;
+ boolean sameDir = next.isDescending() != crit.isAscending();
+ if (direction == null)
+ {
+ // detect index direction (whether the order given by sort is the same as the index)
+ direction = sameDir;
+ }
+ else
+ {
+ // just test whether the direction is kept and the index is a match (even if completely
+ // reversed). If a discrepancy is detected a warning is issued, but the index is accepted
+ // TODO: in the latter case there is probably a programming error. The index will not be
+ // matched server-side and the query will be slow!
+ if (direction != sameDir && badDirection == -1)
+ {
+ badDirection = i;
+ }
+ }
}
if (match)
{
indexName = index.getName().intern();
- indexBySort.put(key, indexName);
+ ret = Pair.of(indexName, direction);
sorters.put(key, new DMOSorter(buffer.getDatabase(), dmoIface, scList));
+ if (badDirection != -1 && LOG.isLoggable(Level.WARNING))
+ {
+ LOG.log(Level.WARNING,
+ "The sort properties (" + sort + ") match the index components (" + index +
+ ") but the direction of " + badDirection + " component is not consistent with " +
+ "the first one. The index will NOT be used!");
+ }
break;
}
}
}
- if (LOG.isLoggable(Level.FINE))
+ if (LOG_FINE)
{
LOG.log(Level.FINE,
"Index for sort phrase '" + sort + "': " +
@@ -890,10 +951,11 @@
if (indexName == null)
{
- indexBySort.put(key, "");
+ ret = Pair.of("", true);
}
- return indexName;
+ indexBySort.put(key, ret);
+ return ret;
}
/**
@@ -919,9 +981,10 @@
throws PersistenceException
{
Set union = null;
- Class extends Record> dmoClass = DBUtils.dmoClassForEntity(entity);
- Iterator iter = DmoMetadataManager.getDmoInfo(dmoClass).getDatabaseIndexes();
- Map mappedCols = DatabaseManager.getColumnToPropertyMap(database, dmoClass, null, true);
+ DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(entity, null);
+ Iterator iter = dmoInfo.getDatabaseIndexes();
+ Map mappedCols = DatabaseManager.getColumnToPropertyMap(
+ database, dmoInfo.getImplementationClass(), null, true);
if (iter == null)
{
@@ -979,7 +1042,7 @@
}
}
- if (LOG.isLoggable(Level.FINE))
+ if (LOG_FINE)
{
LOG.log(Level.FINE,
"Indexed properties " + (unique ? "[unique]" : "[all]") +
=== modified file 'src/com/goldencode/p2j/persist/P2JIndex.java'
--- src/com/goldencode/p2j/persist/P2JIndex.java 2020-09-25 16:00:36 +0000
+++ src/com/goldencode/p2j/persist/P2JIndex.java 2021-01-25 07:55:58 +0000
@@ -23,6 +23,7 @@
** 012 ECF 20150201 Added components(boolean) method.
** 013 ECF 20200906 New ORM implementation.
** 014 OM 20200925 Separated namespaces for index components.
+** 015 IAS 20201224 Added word tables support
*/
/*
@@ -143,6 +144,9 @@
/** Does this index define a unique constraint for its table? */
private boolean unique;
+ /** Word table name (for word index)*/
+ private String wordTableName;
+
/**
* Convenience constructor.
*
@@ -176,13 +180,44 @@
* true if this index is a word index, else
* false.
*/
- public P2JIndex(String table, String name, boolean unique, boolean primary, boolean word)
+ public P2JIndex(String table, String name, boolean unique,
+ boolean primary, boolean word)
+ {
+ this(table, name, unique, primary, word, null);
+ }
+
+ /**
+ * Convenience constructor.
+ *
+ * @param table
+ * Name of the table with which the index is associated.
+ * @param name
+ * Index name.
+ * @param unique
+ * true if this index defines a unique constraint
+ * for its table, else false.
+ * @param primary
+ * true if this index is the primary index for its
+ * table, else false.
+ * @param word
+ * true if this index is a word index, else
+ * false.
+ * @param wordTableName
+ * Word table name (only used for word index).
+ */
+ public P2JIndex(String table,
+ String name,
+ boolean unique,
+ boolean primary,
+ boolean word,
+ String wordTableName)
{
this.table = table;
this.name = name;
this.unique = unique;
this.primary = primary;
this.word = word;
+ this.wordTableName = wordTableName;
}
/**
@@ -861,6 +896,27 @@
}
/**
+ * Get word table names by dialect
+ *
+ * @return Word table name.
+ */
+ public String getWordTableName()
+ {
+ return wordTableName;
+ }
+
+ /**
+ * Set word table names
+ *
+ * @param wordTableNames
+ * Word table names by dialect.
+ */
+ public void setWordTableName(String wordTableName)
+ {
+ this.wordTableName = wordTableName;
+ }
+
+ /**
* Set the table with which this index is associated.
*
* @param table
=== modified file 'src/com/goldencode/p2j/persist/P2JQuery.java'
--- src/com/goldencode/p2j/persist/P2JQuery.java 2020-09-18 09:44:42 +0000
+++ src/com/goldencode/p2j/persist/P2JQuery.java 2020-10-24 00:11:03 +0000
@@ -782,7 +782,7 @@
*
* @return {@code true} on success.
*/
- /*public*/ boolean repositionByID(Long... joinIDs);
+ public boolean repositionByID(Long... joinIDs);
/**
* Reposition the cursor to the specified row in the result set. The row
=== modified file 'src/com/goldencode/p2j/persist/Persistence.java'
--- src/com/goldencode/p2j/persist/Persistence.java 2020-09-20 08:07:24 +0000
+++ src/com/goldencode/p2j/persist/Persistence.java 2021-01-29 19:50:19 +0000
@@ -555,6 +555,9 @@
** OM 20200919 Improved access to dmo metadata.
** ECF 20200919 Eliminate use of Session.refresh where no longer necessary.
** AIL 20200919 Initialize TemporaryDatabaseManager.
+** ECF 20201026 Increase size of query LRU cache.
+** OM 20201120 Avoid UniqueResultException reaching client UI.
+** IAS 20201125 Do not re-use re-written queries
*/
/*
@@ -616,6 +619,7 @@
import java.sql.*;
import java.util.*;
import java.util.Date;
+import java.util.concurrent.*;
import java.util.function.*;
import java.util.logging.*;
import com.goldencode.cache.*;
@@ -1379,6 +1383,9 @@
* value may allow a JDBC driver to use a server-side cursor to fetch smaller batches of
* results at a time. Consult your JDBC driver documentation and/or source code to determine
* whether this is an issue for your JDBC implementation.
+ *
+ * @param
+ * Type of object being listed.
*
* @param entities
* Names of the DMO entities associated with this query statement.
@@ -1395,6 +1402,8 @@
* an offset of 0 is used by default.
* @param readOnlyQuery
* {@code true} to execute the query in read-only mode, else {@code false}.
+ * @param noFlush
+ * No longer used. Should be removed.
*
* @return A list of results, or {@code null} if no result was found.
*
@@ -2557,6 +2566,9 @@
*
* @return Merged DMO.
*
+ * @throws PersistenceException
+ * in error
+ *
* @deprecated
*/
public Record merge(Record dmo)
@@ -2906,7 +2918,7 @@
// retrieve fully qualified name of LockManager implementation class and instantiate it
String path = "database/" + database.getName() + "/p2j/lock_manager/class";
String className = Utils.getDirectoryNodeString(null, path, DEFAULT_LOCK_MANAGER, false);
- LockManager manager = (LockManager) Class.forName(className).newInstance();
+ LockManager manager = (LockManager) Class.forName(className).newInstance();
manager.setDatabase(database);
// register lock table updater with lock manager if the former exists
@@ -3609,14 +3621,9 @@
}
}
- if (exc != null)
- {
- throw new PersistenceException(msg, errnum, exc);
- }
- else
- {
- throw new PersistenceException(msg, errnum);
- }
+ // NOTE: do not add the UniqueResultException as cause for the new exception because it will leak
+ // into UI (see ErrorManager.compileErrorEntries())
+ throw new PersistenceException(msg, errnum);
}
/**
@@ -3956,7 +3963,7 @@
class Context
{
/** Cache of FQL strings, with or without max results and start offsets, to queries. */
- private final ExpiryCache staticQueryCache = new LRUCache<>(100);
+ private final ExpiryCache staticQueryCache = new LRUCache<>(1000);
/** Context-local buffer manager */
private final BufferManager bufferManager = BufferManager.get();
@@ -4094,12 +4101,19 @@
query = staticQueryCache.get(key);
if (query == null)
{
- query = Session.createQuery(fql);
- // NOTE: the FQL string is not converted to SQL at this time. The conversion
- // is performed once, lazily, when the first navigation is required by a
- // call of list(), scroll(), or uniqueResult() on the new query
-
- staticQueryCache.put(key, query);
+ String s = fql;
+ query = Session.createQuery(q ->
+ {
+ synchronized (staticQueryCache)
+ {
+ // NOTE: the FQL string is not converted to SQL at this time. The conversion
+ // is performed once, lazily, when the first navigation is required by a
+ // call of list(), scroll(), or uniqueResult() on the new query
+
+ staticQueryCache.put(key, q);
+ }
+ },
+ fql);
}
// update [maxResults] and [startOffset] because the cached query might have set
=== modified file 'src/com/goldencode/p2j/persist/PreselectQuery.java'
--- src/com/goldencode/p2j/persist/PreselectQuery.java 2020-10-01 17:13:26 +0000
+++ src/com/goldencode/p2j/persist/PreselectQuery.java 2021-01-13 21:04:41 +0000
@@ -561,6 +561,9 @@
** AIL 20200910 Restrict resetting offSet flag only for browsed queries.
** ECF 20200919 Persistence.load API signature change.
** OM 20201001 Dropped redundant ORDER BY elements in multi-table queries.
+** OM 20201007 Optimized SortCriterion by using DmoMeta data instead of map lookups.
+** _multiplex sort component matches the index direction.
+** OM 20201120 Adjusted error messages.
*/
/*
@@ -2980,7 +2983,8 @@
if (!repositionByID(serIDs, false))
{
- ErrorManager.recordOrThrowError(7331, "Cannot reposition query to recid/rowid(s) given");
+ ErrorManager.recordOrThrowError(7331, "", "");
+ // "Cannot reposition query to recid/rowid(s) given".
}
}
@@ -3018,9 +3022,8 @@
if (!repositionByID(serIDs, false))
{
- ErrorManager.recordOrThrowError(
- 7331,
- "Cannot reposition query to recid/rowid(s) given");
+ ErrorManager.recordOrThrowError(7331, "", "");
+ // "Cannot reposition query to recid/rowid(s) given".
}
}
@@ -3518,7 +3521,7 @@
*/
public int getTableCount()
{
- return components.size();
+ return components == null ? 0 : components.size();
}
/**
@@ -4829,7 +4832,7 @@
if (buffer == null)
{
throw new PersistenceException(
- "Order by clause references an unrecognized buffer: " +alias + " (" + part + ")");
+ "Order by clause references an unrecognized buffer: " + alias + " (" + part + ")");
}
// Find or create the sub-list in which to store SortCriterion
@@ -4841,14 +4844,8 @@
subSortMap.put(buffer, subSortCrit);
}
- // Create the SortCriterion instance and add it to the master list
- // and to the appropriate sub-list.
-
-// String schema = buffer.getSchema();
- Class extends DataModelObject> dmoIface = buffer.getDMOInterface();
- Class extends Record> dmoClass = buffer.getDMOImplementationClass();
-
- SortCriterion crit = new SortCriterion(buffer, part, /*schema,*/ dmoIface, dmoClass);
+ // create the SortCriterion instance and add it to the master list and to the appropriate sub-list
+ SortCriterion crit = new SortCriterion(buffer, part);
sortCriteria.add(crit);
subSortCrit.add(crit);
@@ -4904,13 +4901,16 @@
if (multiplex)
{
- Class extends DataModelObject> dmoIface = buffer.getDMOInterface();
- Class extends Record> dmoClass = buffer.getDMOImplementationClass();
- String text = buffer.getDMOAlias() + "." + TemporaryBuffer.MULTIPLEX_FIELD_NAME + " asc";
+ // the _multiplex is injected so that the query to benefit from the matching index
+
+ Boolean ascIndex = IndexHelper.get(buffer.getDatabase()).getIndexDirection(buffer, sort);
+ StringBuilder sb = new StringBuilder();
+ sb.append(buffer.getDMOAlias()).append(".").append(TemporaryBuffer.MULTIPLEX_FIELD_NAME);
+ sb.append(ascIndex == null || ascIndex ? " asc" : " desc");
try
{
- workList.add(new SortCriterion(buffer, text, dmoIface, dmoClass));
+ workList.add(new SortCriterion(buffer, sb.toString()));
}
catch (PersistenceException exc)
{
=== modified file 'src/com/goldencode/p2j/persist/PresortQuery.java'
--- src/com/goldencode/p2j/persist/PresortQuery.java 2020-10-01 17:13:26 +0000
+++ src/com/goldencode/p2j/persist/PresortQuery.java 2020-10-09 21:13:00 +0000
@@ -89,6 +89,7 @@
** 031 ECF 20181106 Added runtime support for {FIRST|LAST}-OF methods.
** 032 ECF 20200906 New ORM implementation.
** 033 OM 20201001 Dropped redundant ORDER BY elements in multi-table queries.
+** OM 20201007 Optimized SortCriterion by using DmoMeta data instead of map lookups.
*/
/*
@@ -1492,14 +1493,8 @@
while (iter.hasNext())
{
RecordBuffer buffer = iter.next().getBuffer();
- Class extends DataModelObject> dmoIface = buffer.getDMOInterface();
- Class extends Record> dmoClass = buffer.getDMOImplementationClass();
-
SortCriterion crit = new SortCriterion(
- buffer,
- buffer.getDMOAlias() + "." + DatabaseManager.PRIMARY_KEY + " asc",
- dmoIface,
- dmoClass);
+ buffer, buffer.getDMOAlias() + "." + DatabaseManager.PRIMARY_KEY + " asc");
sortCriteria.add(crit);
}
=== modified file 'src/com/goldencode/p2j/persist/PropertyDefinition.java'
--- src/com/goldencode/p2j/persist/PropertyDefinition.java 2020-09-24 17:41:02 +0000
+++ src/com/goldencode/p2j/persist/PropertyDefinition.java 2021-01-13 21:04:41 +0000
@@ -4,18 +4,20 @@
**
** Copyright (c) 2013-2020, Golden Code Development Corporation.
**
-** -#- -I- --Date-- ---------------------------------Description----------------------------------
+** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 CA 20130618 Created initial version.
** 002 ECF 20140623 Removed Hibernate dependency. This update simplifies the use of the type
** variable; this is a short-term workaround which breaks some result set
-** metadata functionality (not currently in use), but it needs further
-** analysis/work.
+** metadata functionality (not currently in use), but it needs further analysis/work.
** 003 ECF 20140626 Simplified type check.
** 004 SVL 20140709 Added legacyName field.
** 005 CA 20190812 Added toString().
** 006 IAS 20200908 Rework (de)serialization.
** 007 IAS 20200922 Get rid of possible NPE on serialization.
+** SVL 20201030 Added format, label and columnLabel fields.
+** OM 20201120 Added SCHEMA-MARSHAL implementation.
*/
+
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
@@ -71,13 +73,19 @@
package com.goldencode.p2j.persist;
-import static com.goldencode.util.NativeTypeSerializer.*;
import java.io.*;
import java.util.*;
+import static com.goldencode.p2j.persist.AbstractTempTable.*;
+import static com.goldencode.util.NativeTypeSerializer.*;
/**
* Container for a DMO property's metadata: name, type and extent. Used to send the metadata of a
* DMO to a remote side, when remote appserver calls are involved.
+ *
+ * TODO: add the other schema attributes of a property definition: help, initial, mandatory, validateMessage,
+ * validationExpression, description, decimals (for decimal type), caseSensitive (for character type)
+ * codePage, serializeName, xmlNodeName, xmlDataType, etc.
+ * All these will be serialized only if {@code schemaMarshalLevel == SM_FULL}.
*/
public class PropertyDefinition
implements Externalizable
@@ -99,8 +107,23 @@
/** The legacy name of this property. */
private String legacyName;
-
- static
+
+ /** The format of this property. */
+ private String format;
+
+ /** The label of this property. */
+ private String label;
+
+ /** The column label of this property. */
+ private String columnLabel;
+
+ /**
+ * The SCHEMA-MARSHAL level for the parent temp-table. It is never {@code SM_DEFAULT} when object is to
+ * be serialized. Always {@code SM_DEFAULT} for a read object.
+ */
+ private int schemaMarshalLevel = SM_DEFAULT;
+
+ static
{
primitiveClasses.put("byte", byte.class);
primitiveClasses.put("short", short.class);
@@ -134,8 +157,7 @@
throws ClassNotFoundException
{
this.name = name;
- Class> cls = primitiveClasses.containsKey(type) ? primitiveClasses.get(type)
- : Class.forName(type);
+ Class> cls = primitiveClasses.containsKey(type) ? primitiveClasses.get(type) : Class.forName(type);
verifyType(cls);
this.type = cls;
this.extent = NO_EXTENT;
@@ -152,7 +174,7 @@
* The extent of this property.
*
* @throws ClassNotFoundException
- * If the given type can not be resolved to a valid class.
+ * If the given {@code type} can not be resolved to a valid class.
*/
public PropertyDefinition(String name, String type, int extent)
throws ClassNotFoundException
@@ -186,11 +208,7 @@
*/
public PropertyDefinition(String name, Class> type, String legacyName)
{
- this.name = name;
- verifyType(type);
- this.type = type;
- this.extent = NO_EXTENT;
- this.legacyName = legacyName;
+ this(name, type, legacyName, null, null, null);
}
/**
@@ -222,11 +240,80 @@
*/
public PropertyDefinition(String name, Class> type, int extent, String legacyName)
{
- this(name, type);
+ this(name, type, extent, legacyName, null, null, null);
+ }
+
+ /**
+ * Create a new, no-extent, property definition.
+ *
+ * @param name
+ * The name of this property.
+ * @param type
+ * A class specifying the type of this property.
+ * @param legacyName
+ * The legacy name of this property.
+ * @param format
+ * The format of this property.
+ * @param label
+ * The label of this property.
+ * @param columnLabel
+ * The column label of this property.
+ */
+ public PropertyDefinition(String name,
+ Class> type,
+ String legacyName,
+ String format,
+ String label,
+ String columnLabel)
+ {
+ this(name, type, NO_EXTENT, legacyName, format, label, columnLabel);
+ }
+
+ /**
+ * Create a new property definition.
+ *
+ * @param name
+ * The name of this property.
+ * @param type
+ * A class specifying the type of this property.
+ * @param legacyName
+ * The legacy name of this property.
+ * @param format
+ * The format of this property.
+ * @param label
+ * The label of this property.
+ * @param columnLabel
+ * The column label of this property.
+ */
+ public PropertyDefinition(String name,
+ Class> type,
+ int extent,
+ String legacyName,
+ String format,
+ String label,
+ String columnLabel)
+ {
+ this.name = name;
+ verifyType(type);
+ this.type = type;
this.extent = extent;
this.legacyName = legacyName;
- }
-
+ this.format = format;
+ this.label = label;
+ this.columnLabel = columnLabel;
+ }
+
+ /**
+ * The SCHEMA-MARSHAL level to be used when serialization of the property.
+ *
+ * @param schemaMarshalLevel
+ * The configured SCHEMA-MARSHAL level. See static constants in {@link AbstractTempTable}.
+ */
+ public void setSchemaMarshalLevel(int schemaMarshalLevel)
+ {
+ this.schemaMarshalLevel = schemaMarshalLevel;
+ }
+
/**
* Get the type of this property.
*
@@ -236,7 +323,7 @@
{
return type;
}
-
+
/**
* Get the extent of this property.
*
@@ -278,6 +365,36 @@
}
/**
+ * Get format of this property.
+ *
+ * @return format of this property.
+ */
+ public String getFormat()
+ {
+ return format;
+ }
+
+ /**
+ * Get label of this property.
+ *
+ * @return label of this property.
+ */
+ public String getLabel()
+ {
+ return label;
+ }
+
+ /**
+ * Get column label of this property.
+ *
+ * @return column label of this property.
+ */
+ public String getColumnLabel()
+ {
+ return columnLabel;
+ }
+
+ /**
* Send the property definition to the specified output destination.
*
* @param out
@@ -289,12 +406,15 @@
public final void writeExternal(ObjectOutput out)
throws IOException
{
- writeString(out, name);
- writeString(out, type == null ? null : type.getName());
- out.writeInt(extent);
- writeString(out, legacyName);
+ writeString(out, (schemaMarshalLevel == SM_NONE) ? null : name);
+ writeString(out, (type == null || schemaMarshalLevel == SM_NONE) ? null : type.getName());
+ out.writeInt((schemaMarshalLevel == SM_NONE) ? 0 : extent);
+ writeString(out, (schemaMarshalLevel == SM_NONE) ? null : legacyName);
+ writeString(out, (schemaMarshalLevel == SM_FULL) ? format : null);
+ writeString(out, (schemaMarshalLevel == SM_FULL) ? label : null);
+ writeString(out, (schemaMarshalLevel == SM_FULL) ? columnLabel : null);
}
-
+
/**
* Read the property definition from the specified input source.
*
@@ -303,7 +423,9 @@
*
* @throws IOException
* In case of I/O errors.
- * @throws ClassNotFoundException
+ *
+ * @throws ClassNotFoundException
+ * If the class of property could not be found/loaded.
*/
public final void readExternal(ObjectInput in)
throws IOException,
@@ -311,17 +433,20 @@
{
name = readString(in);
String tn = readString(in);
- type = (Class>)(tn == null ? null : Class.forName(tn));
+ type = (tn == null) ? null : Class.forName(tn);
extent = in.readInt();
legacyName = readString(in);
+ format = readString(in);
+ label = readString(in);
+ columnLabel = readString(in);
}
/**
* Verify the given type is not null.
- *
+ *
* @param type
* The type to check.
- *
+ *
* @throws NullPointerException
* If type is null.
*/
@@ -341,12 +466,24 @@
@Override
public String toString()
{
- String s = name + "[legacy: " + legacyName + "]: " + type.getSimpleName();
+ StringBuilder sb = new StringBuilder();
+ sb.append(name).append("[legacy: ").append(legacyName).append("]: ").append(type.getSimpleName());
if (extent != NO_EXTENT && extent != 0)
{
- s = s + "[" + extent + "]";
+ sb.append("[").append(extent).append("]");
}
- return s;
+ return sb.toString();
+ }
+
+ /**
+ * Configures the SCHEMA-MARSHAL level used for serialization.
+ *
+ * @param schemaMarshalLevel
+ * The new SCHEMA-MARSHAL level. See {@link AbstractTempTable} static constants for details.
+ */
+ public void setMarshalLevel(int schemaMarshalLevel)
+ {
+ this.schemaMarshalLevel = schemaMarshalLevel;
}
}
=== modified file 'src/com/goldencode/p2j/persist/QueryWrapper.java'
--- src/com/goldencode/p2j/persist/QueryWrapper.java 2020-09-18 09:44:42 +0000
+++ src/com/goldencode/p2j/persist/QueryWrapper.java 2021-01-21 22:49:45 +0000
@@ -4,7 +4,7 @@
**
** Copyright (c) 2005-2020, Golden Code Development Corporation.
**
-** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
+** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20060430 @25885 Created initial version. Merged contents of
** AbstractQueryWrapper, CompoundQueryWrapper,
** PreselectQueryWrapper, and RandomAccessQuery-
@@ -239,6 +239,8 @@
** CA 20200918 INDEX-INFORMATION requires to interpret the 'execute' method for a dynamic query
** without evaluating the dynamic calls in the WHERE.
** Fixed multiple QUERY-OPEN() calls - must close the query before reopening.
+** VVT 20210108 repositionByID() fixed to match 4gl behaviour: see #5038-39
+** OM 20201120 Added OFF-END callback.
*/
/*
@@ -365,11 +367,14 @@
/** Default delegate to use before a real one is assigned */
private static final P2JQuery DEFAULT_DELEGATE = new DefaultDelegate();
+ /** The {@code OFF-END} event string constant in upper case. */
+ private static final String OFF_END_EVENT = "OFF-END";
+
/** Delegate query */
private P2JQuery delegate = null;
/** Reposition listeners registered with the delegate query */
- private WeakList repoListeners = new WeakList<>();
+ private final WeakList repoListeners = new WeakList<>();
/** Should delegate be a scrolling query? */
private boolean scrolling = false;
@@ -384,7 +389,7 @@
private boolean browsed = false;
/** Opened listeners registered to get the message about query has been opened. */
- private List openedListeners = new ArrayList<>();
+ private final List openedListeners = new ArrayList<>();
/** Flag indicating if the query is closing. */
private boolean closing = false;
@@ -392,6 +397,9 @@
/** Flag indicating if the query was explicitly closed or is not open. */
private boolean closed = true;
+ /** Current off-end state. Used to detect when off-end state was encountered and fire OFF-END callback. */
+ private boolean offEnd = false;
+
/** Flag indicating if the query has been advanced (via GET NEXT) to the next record. */
private boolean advanced = false;
@@ -437,10 +445,13 @@
* dynamic query must appear exactly one time in FOR clause.
*/
private List substBuffers = null;
-
+
/** External handler for create/delete result list entry methods. */
private ResultListHandler externalResultListHandler = null;
+ /** The only possible callback. {@code null} if not set.*/
+ private CallbackData callback = null;
+
/**
* Constructor which creates a static query and sets query scrolling mode for all delegate
* queries assigned to this wrapper.
@@ -702,6 +713,8 @@
{
registerCleaner();
}
+
+ offEnd = delegate._isOffEnd();
}
/**
@@ -1787,6 +1800,7 @@
advanced = true;
handleQueryOffEnd(() -> getDelegate().first());
+ maybeFireCallback();
}
/**
@@ -1810,6 +1824,7 @@
advanced = true;
handleQueryOffEnd(() -> getDelegate().first(lockType));
+ maybeFireCallback();
}
/**
@@ -1937,6 +1952,7 @@
}
handleQueryOffEnd(() -> getDelegate().last());
+ maybeFireCallback();
}
/**
@@ -1957,8 +1973,9 @@
{
return;
}
-
+
handleQueryOffEnd(() -> getDelegate().last(lockType));
+ maybeFireCallback();
}
/**
@@ -2092,6 +2109,7 @@
advanced = true;
getDelegate().next();
+ maybeFireCallback();
}
/**
@@ -2120,6 +2138,7 @@
advanced = true;
getDelegate().next(lockType);
+ maybeFireCallback();
}
/**
@@ -2253,6 +2272,7 @@
}
getDelegate().previous();
+ maybeFireCallback();
}
/**
@@ -2279,6 +2299,7 @@
}
getDelegate().previous(lockType);
+ maybeFireCallback();
}
/**
@@ -2406,6 +2427,7 @@
}
getDelegate().current();
+ maybeFireCallback();
}
/**
@@ -2427,6 +2449,7 @@
}
getDelegate().current(lockType);
+ maybeFireCallback();
}
/**
@@ -2444,6 +2467,7 @@
public void unique()
{
getDelegate().unique();
+ maybeFireCallback();
}
/**
@@ -2465,6 +2489,7 @@
public void unique(LockType lockType)
{
getDelegate().unique(lockType);
+ maybeFireCallback();
}
/**
@@ -2851,6 +2876,7 @@
}
getDelegate().repositionByID(id);
+ maybeFireCallback();
}
/**
@@ -2868,6 +2894,9 @@
* right to coincide with the records being joined by the
* underlying query.
*
+ * If an array element is unknown, this element and all subsequent elements
+ * are ignored.
+ *
* @throws ErrorConditionException
* if this query is not a scrolling query;
* if any of the specified IDs represents the unknown value.
@@ -2878,8 +2907,20 @@
{
return;
}
-
+
+ for(int i = 0; i < joinIDs.length; i++)
+ {
+ if(joinIDs[i].isUnknown())
+ {
+ rowid[] newIds = new rowid[i];
+ System.arraycopy(joinIDs, 0, newIds, 0, i);
+ joinIDs = newIds;
+ break;
+ }
+ }
+
getDelegate().repositionByID(id1, joinIDs);
+ maybeFireCallback();
}
/**
@@ -2901,7 +2942,9 @@
@Override
public boolean repositionByID(Long... joinIDs)
{
- return getDelegate().repositionByID(joinIDs);
+ boolean ret = getDelegate().repositionByID(joinIDs);
+ maybeFireCallback();
+ return ret;
}
/**
@@ -2924,8 +2967,9 @@
{
return;
}
-
+
getDelegate().reposition(row);
+ maybeFireCallback();
}
/**
@@ -2952,6 +2996,7 @@
*/
getDelegate().reposition(row);
+ maybeFireCallback();
}
/**
@@ -2980,7 +3025,9 @@
*/
public boolean forward(NumberType rows)
{
- return getDelegate().forward(rows);
+ boolean ret = getDelegate().forward(rows);
+ maybeFireCallback();
+ return ret;
}
/**
@@ -3008,7 +3055,9 @@
*/
public boolean forward(int rows)
{
- return getDelegate().forward(rows);
+ boolean ret = getDelegate().forward(rows);
+ maybeFireCallback();
+ return ret;
}
/**
@@ -3037,7 +3086,9 @@
*/
public boolean backward(NumberType rows)
{
- return getDelegate().backward(rows);
+ boolean ret = getDelegate().backward(rows);
+ maybeFireCallback();
+ return ret;
}
/**
@@ -3065,7 +3116,9 @@
*/
public boolean backward(int rows)
{
- return getDelegate().backward(rows);
+ boolean ret = getDelegate().backward(rows);
+ maybeFireCallback();
+ return ret;
}
/**
@@ -4354,12 +4407,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return getDelegate().getNext();
+
+ logical ret = getDelegate().getNext();
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-NEXT method (KW_GET_NEXT).
*
@@ -4379,12 +4434,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return getDelegate().getNext(lockType);
+
+ logical ret = getDelegate().getNext(lockType);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-NEXT method (KW_GET_NEXT).
*
@@ -4407,12 +4464,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return getDelegate().getNext(lock);
+
+ logical ret = getDelegate().getNext(lock);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-NEXT method (KW_GET_NEXT).
*
@@ -4435,12 +4494,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return getDelegate().getNext(lock);
+
+ logical ret = getDelegate().getNext(lock);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-NEXT method (KW_GET_NEXT).
*
@@ -4465,12 +4526,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return getDelegate().getNext(lock, nowait);
+
+ logical ret = getDelegate().getNext(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-NEXT method (KW_GET_NEXT).
*
@@ -4495,12 +4558,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return getDelegate().getNext(lock, nowait);
+
+ logical ret = getDelegate().getNext(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-NEXT method (KW_GET_NEXT).
*
@@ -4525,12 +4590,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return getDelegate().getNext(lock, nowait);
+
+ logical ret = getDelegate().getNext(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-NEXT method (KW_GET_NEXT).
*
@@ -4555,12 +4622,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return getDelegate().getNext(lock, nowait);
+
+ logical ret = getDelegate().getNext(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-PREV method (KW_GET_PREV).
*
@@ -4577,10 +4646,12 @@
{
return new logical(false);
}
-
- return getDelegate().getPrevious();
+
+ logical ret = getDelegate().getPrevious();
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-PREV method (KW_GET_PREV).
*
@@ -4603,10 +4674,12 @@
{
return new logical(false);
}
-
- return getDelegate().getPrevious(lock);
+
+ logical ret = getDelegate().getPrevious(lock);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-PREV method (KW_GET_PREV).
*
@@ -4629,10 +4702,12 @@
{
return new logical(false);
}
-
- return getDelegate().getPrevious(lockType);
+
+ logical ret = getDelegate().getPrevious(lockType);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-PREV method (KW_GET_PREV).
*
@@ -4655,10 +4730,12 @@
{
return new logical(false);
}
-
- return getDelegate().getPrevious(lock);
+
+ logical ret = getDelegate().getPrevious(lock);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-PREV method (KW_GET_PREV).
*
@@ -4683,10 +4760,12 @@
{
return new logical(false);
}
-
- return getDelegate().getPrevious(lock, nowait);
+
+ logical ret = getDelegate().getPrevious(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-PREV method (KW_GET_PREV).
*
@@ -4711,10 +4790,12 @@
{
return new logical(false);
}
-
- return getDelegate().getPrevious(lock, nowait);
+
+ logical ret = getDelegate().getPrevious(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-PREV method (KW_GET_PREV).
*
@@ -4739,10 +4820,12 @@
{
return new logical(false);
}
-
- return getDelegate().getPrevious(lock, nowait);
+
+ logical ret = getDelegate().getPrevious(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-PREV method (KW_GET_PREV).
*
@@ -4767,10 +4850,12 @@
{
return new logical(false);
}
-
- return getDelegate().getPrevious(lock, nowait);
+
+ logical ret = getDelegate().getPrevious(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-FIRST method (KW_GET_1ST).
*
@@ -4787,12 +4872,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return handleQueryOffEnd(() -> getDelegate().getFirst());
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getFirst());
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-FIRST method (KW_GET_1ST).
*
@@ -4815,12 +4902,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return handleQueryOffEnd(() -> getDelegate().getFirst(lock));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getFirst(lock));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-FIRST method (KW_GET_1ST).
*
@@ -4843,12 +4932,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return handleQueryOffEnd(() -> getDelegate().getFirst(lock));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getFirst(lock));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-FIRST method (KW_GET_1ST).
*
@@ -4871,12 +4962,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return handleQueryOffEnd(() -> getDelegate().getFirst(lockType));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getFirst(lockType));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-FIRST method (KW_GET_1ST).
*
@@ -4901,12 +4994,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return handleQueryOffEnd(() -> getDelegate().getFirst(lock, nowait));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getFirst(lock, nowait));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-FIRST method (KW_GET_1ST).
*
@@ -4931,12 +5026,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return handleQueryOffEnd(() -> getDelegate().getFirst(lock, nowait));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getFirst(lock, nowait));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-FIRST method (KW_GET_1ST).
*
@@ -4961,12 +5058,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return handleQueryOffEnd(() -> getDelegate().getFirst(lock, nowait));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getFirst(lock, nowait));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-FIRST method (KW_GET_1ST).
*
@@ -4991,12 +5090,14 @@
{
return new logical(false);
}
-
+
advanced = true;
-
- return handleQueryOffEnd(() -> getDelegate().getFirst(lock, nowait));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getFirst(lock, nowait));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-LAST method (KW_GET_LAST).
*
@@ -5013,10 +5114,12 @@
{
return new logical(false);
}
-
- return handleQueryOffEnd(() -> getDelegate().getLast());
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getLast());
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-LAST method (KW_GET_LAST).
*
@@ -5039,10 +5142,12 @@
{
return new logical(false);
}
-
- return handleQueryOffEnd(() -> getDelegate().getLast(lock));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getLast(lock));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-LAST method (KW_GET_LAST).
*
@@ -5065,10 +5170,12 @@
{
return new logical(false);
}
-
- return handleQueryOffEnd(() -> getDelegate().getLast(lock));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getLast(lock));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-LAST method (KW_GET_LAST).
*
@@ -5091,10 +5198,12 @@
{
return new logical(false);
}
-
- return handleQueryOffEnd(() -> getDelegate().getLast(lockType));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getLast(lockType));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-LAST method (KW_GET_LAST).
*
@@ -5119,10 +5228,12 @@
{
return new logical(false);
}
-
- return handleQueryOffEnd(() -> getDelegate().getLast(lock, nowait));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getLast(lock, nowait));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-LAST method (KW_GET_LAST).
*
@@ -5147,10 +5258,12 @@
{
return new logical(false);
}
-
- return handleQueryOffEnd(() -> getDelegate().getLast(lock, nowait));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getLast(lock, nowait));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-LAST method (KW_GET_LAST).
*
@@ -5175,10 +5288,12 @@
{
return new logical(false);
}
-
- return handleQueryOffEnd(() -> getDelegate().getLast(lock, nowait));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getLast(lock, nowait));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-LAST method (KW_GET_LAST).
*
@@ -5203,10 +5318,12 @@
{
return new logical(false);
}
-
- return handleQueryOffEnd(() -> getDelegate().getLast(lock, nowait));
+
+ logical ret = handleQueryOffEnd(() -> getDelegate().getLast(lock, nowait));
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-CURRENT() method (KW_GET_CUR).
*
@@ -5220,10 +5337,12 @@
{
return new logical(false);
}
-
- return getDelegate().getCurrent();
+
+ logical ret = getDelegate().getCurrent();
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-CURRENT() method (KW_GET_CUR).
*
@@ -5240,10 +5359,12 @@
{
return new logical(false);
}
-
- return getDelegate().getCurrent(lock);
+
+ logical ret = getDelegate().getCurrent(lock);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-CURRENT() method (KW_GET_CUR).
*
@@ -5260,10 +5381,12 @@
{
return new logical(false);
}
-
- return getDelegate().getCurrent(lock);
+
+ logical ret = getDelegate().getCurrent(lock);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-CURRENT() method (KW_GET_CUR).
*
@@ -5279,10 +5402,12 @@
{
return new logical(false);
}
-
- return getDelegate().getCurrent(lockType);
+
+ logical ret = getDelegate().getCurrent(lockType);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-CURRENT() method (KW_GET_CUR).
*
@@ -5301,10 +5426,12 @@
{
return new logical(false);
}
-
- return getDelegate().getCurrent(lock, nowait);
+
+ logical ret = getDelegate().getCurrent(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-CURRENT() method (KW_GET_CUR).
*
@@ -5323,10 +5450,12 @@
{
return new logical(false);
}
-
- return getDelegate().getCurrent(lock, nowait);
+
+ logical ret = getDelegate().getCurrent(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-CURRENT() method (KW_GET_CUR).
*
@@ -5345,10 +5474,12 @@
{
return new logical(false);
}
-
- return getDelegate().getCurrent(lock, nowait);
+
+ logical ret = getDelegate().getCurrent(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
-
+
/**
* Conversion of GET-CURRENT() method (KW_GET_CUR).
*
@@ -5367,8 +5498,10 @@
{
return new logical(false);
}
-
- return getDelegate().getCurrent(lock, nowait);
+
+ logical ret = getDelegate().getCurrent(lock, nowait);
+ maybeFireCallback();
+ return ret;
}
/**
@@ -5523,6 +5656,208 @@
}
/**
+ * Associates an internal procedure with an ABL callback event.
+ *
+ * @param eventName
+ * The eventName of the event.
+ * @param callback
+ * The eventName of the internal procedure associated with the callback event.
+ * @param context
+ * A handle to a procedure that contains the internal procedure specified by {@code callback}. If
+ * not specified, {@code THIS-PROCEDURE} is used as the procedure context.
+ *
+ * @return {@code true} when the method ends with success.
+ */
+ @Override
+ public logical setCallbackProcedure(String eventName, String callback, handle context)
+ {
+ if (!isCallbackName(eventName))
+ {
+ return new logical(false);
+ }
+
+ if (getDataSet() == null)
+ {
+ ErrorManager.recordOrShowError(11951);
+ // SET-CALLBACK-PROCEDURE is only valid for buffers that are members of a dataset.
+ return new logical(false);
+ }
+
+ if (callback != null)
+ {
+ if (context == null || context.isUnknown())
+ {
+ // defaulting to THIS-PROCEDURE
+ context = ProcedureManager.thisProcedure();
+ }
+ else
+ {
+ if (!context._isValid() || !(context.getResource() instanceof PersistentProcedure))
+ {
+ ErrorManager.recordOrShowError(13273);
+ // Third argument to SET-CALLBACK must be a valid procedure handle or object reference.
+ return new logical(false);
+ }
+ }
+
+ this.callback = new CallbackData(callback, context, null);
+ }
+
+ return new logical(true);
+ }
+
+ /**
+ * Configures a callback. Creates an association between a method within a class instance or an internal
+ * procedure within a persistent procedure, with an ABL callback event.
+ *
+ * @param callbackName
+ * The name of a callback.
+ * @param routineName
+ * The name of a method or an internal procedure to be associated.
+ * @param context
+ * The context in which the callback will be executed.
+ *
+ * @return {@code true} if {@code callbackName} is a correct callback name. The rest of parameters are
+ * ignored (note: manual states otherwise).
+ */
+ @Override
+ public logical setCallback(String callbackName, String routineName, object> context)
+ {
+ if (!isCallbackName(callbackName))
+ {
+ return new logical(false);
+ }
+
+ if (getDataSet() == null)
+ {
+ ErrorManager.recordOrShowError(11951);
+ // SET-CALLBACK-PROCEDURE is only valid for buffers that are members of a dataset.
+ return new logical(false);
+ }
+
+ if (routineName != null)
+ {
+ CallbackData cbd;
+ if (context == null || context.isUnknown())
+ {
+ // defaulting to THIS-OBJECT or to THIS-PROCEDURE, depending on the context
+ if (ObjectOps.thisObject().isUnknown())
+ {
+ cbd = new CallbackData(routineName, ProcedureManager.thisProcedure(), null);
+ }
+ else
+ {
+ cbd = new CallbackData(routineName, null, ObjectOps.thisObject());
+ }
+ }
+ else
+ {
+ cbd = new CallbackData(routineName, null, context);
+ }
+
+ this.callback = cbd;
+ }
+
+ return new logical(true);
+ }
+
+ /**
+ * Applies a callback procedure, which allows execution of a defined event without duplicating the event
+ * procedure definition.
+ *
+ * @param eventName
+ * The event whose callback will be called.
+ *
+ * @return {@code true} if operation is successful.
+ */
+ public logical applyCallback(String eventName)
+ {
+ DataSet ds = getDataSet();
+ if (ds == null)
+ {
+ ErrorManager.recordOrShowError(11949);
+ // APPLY-CALLBACK is only for buffers that are members of datasets.
+ return new logical(false);
+ }
+
+ if (!isCallbackName(eventName))
+ {
+ return new logical(false);
+ }
+
+ return new logical(invokeCallback(asHandle(), this.callback));
+ }
+
+ /**
+ * Retrieves the name of the internal procedure associated with the ABL callback for the specified event.
+ *
+ * @param eventName
+ * The name of the event.
+ *
+ * @return the name of the internal procedure associated with specified event.
+ */
+ @Override
+ public character getCallbackProcName(String eventName)
+ {
+ if (getDataSet() == null)
+ {
+ if (buffers != null && buffers.size() > 0)
+ {
+ ErrorManager.recordOrShowError(12788, getName(), ((BufferImpl) buffers.get(0)).doGetName());
+ // OFF-END CALLBACK not supported for query whose buffer is not in a dataset.
+ return new character();
+ }
+ }
+
+ if (!isCallbackName(eventName))
+ {
+ return new character();
+ }
+
+ if (this.callback == null)
+ {
+ return new character();
+ }
+
+ return new character(this.callback.getTarget());
+ }
+
+ /**
+ * Retrieves the handle of the procedure that contains the internal procedure associated with the ABL
+ * callback for the specified event
+ *
+ * @param eventName
+ * The name of the event.
+ *
+ * @return a handle to the procedure that contains the callback procedure.
+ */
+ @Override
+ public handle getCallbackProcContext(String eventName)
+ {
+ if (getDataSet() == null)
+ {
+ if (buffers != null && buffers.size() > 0)
+ {
+ ErrorManager.recordOrShowError(12788, getName(), ((BufferImpl) buffers.get(0)).doGetName());
+ // OFF-END CALLBACK not supported for query whose buffer is not in a dataset.
+ return new handle();
+ }
+ }
+
+ if (!isCallbackName(eventName))
+ {
+ return new handle();
+ }
+
+ if (this.callback == null || this.callback.getProcedure() == null)
+ {
+ return new handle();
+ }
+
+ return new handle(this.callback.getProcedure());
+ }
+
+ /**
* Specifies the fields included in a record retrieval.
*
* @param dmo
@@ -5551,6 +5886,22 @@
}
/**
+ * Test whether the OFF-END event occurred and if so, fire the callback if one is defined.
+ */
+ private void maybeFireCallback()
+ {
+ if (!offEnd && getDelegate()._isOffEnd())
+ {
+ offEnd = true;
+
+ if (getDataSet() != null && callback != null)
+ {
+ invokeCallback(asHandle(), this.callback);
+ }
+ }
+ }
+
+ /**
* Add the buffers associated with the given query to this QUERY's buffer list.
*
* Will be a no-op when this is a {@link #dynamic} query.
@@ -6228,6 +6579,103 @@
}
/**
+ * Invokes a callback procedure or a callback method. The callback procedure/method must have an
+ * {@code INPUT} parameter of type {@code DATASET} or {@code DATASET-HANDLE}. The passed parameter is this
+ * {@code DataSet} object.
+ *
+ * @param cbd
+ * A {@code CallbackData} object holding the data necessary for invocation. May be {@code null},
+ * in which case this method will silently return {@code yes} even though nothing is performed.
+ * @param self
+ * The object on which the event is applied (buffer or dataset).
+ *
+ * @return {@code true} if no errors were encountered during execution.
+ */
+ protected boolean invokeCallback(handle self, CallbackData cbd)
+ {
+ if (cbd == null)
+ {
+ return true; // yep, nothing to do here
+ }
+
+ try
+ {
+ ErrorManager.setSilent(false);
+
+ SelfManager.pushSelf(self);
+
+ // reference already assumes input-output, do not emit append
+ DataSetParameter dsp = new DataSetParameter(getDataSet(), false, ParameterOption.BY_REFERENCE);
+ if (cbd.getObject() != null)
+ {
+ // TODO:
+ // Method '' for callback must be valid public method. (13452)
+
+ ObjectOps.invokeStandalone(cbd.getObject(), cbd.getTarget(), "I", dsp);
+ }
+ else if (cbd.getProcedure() != null && !cbd.getProcedure().isUnknown())
+ {
+ ControlFlowOps.invokeInWithMode(cbd.getTarget(), cbd.getProcedure(), "I", dsp);
+ }
+
+ // NOTE: sometimes the method return *NO* even if the callback was successfully called
+ return true;
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return false;
+ }
+ finally
+ {
+ SelfManager.popSelf();
+
+ // Do not restore silent-mode!
+ }
+ }
+
+ /**
+ * Obtains the {@code Dataset} of the first buffer of this query, if this is defined.
+ *
+ * @return the {@code Dataset} of the first buffer of this query, if this is defined.
+ */
+ private DataSet getDataSet()
+ {
+ DataSet ds = null;
+ if (buffers != null && buffers.size() > 0)
+ {
+ BufferImpl buffer = (BufferImpl) buffers.get(0);
+ if (buffer.isAfterBuffer())
+ {
+ ds = buffer._dataSet();
+ }
+ }
+ return ds;
+ }
+
+ /**
+ * Test whether a string is a valid CALLBACK name for a {@code DataSet Query}. This method is responsible
+ * for raising the 12352 error if the name specified is unknown or not {@code OFF-END} - the only supported
+ * event for dataset queries.
+ *
+ * @param someName
+ * The name to test.
+ *
+ * @return {@code true} only if {@code someName} is equals to "OFF-END".
+ */
+ private static boolean isCallbackName(String someName)
+ {
+ if (!OFF_END_EVENT.equalsIgnoreCase(someName))
+ {
+ ErrorManager.recordOrShowError(12352, someName != null ? someName : "");
+ // Argument to query callback method must be a valid query event name such as OFF-END
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* A no-op implementation of P2JQuery which is used as the
* default delegate for the enclosing QueryWrapper. This
* implementation behaves as a query which returns no results would,
=== modified file 'src/com/goldencode/p2j/persist/RandomAccessQuery.java'
--- src/com/goldencode/p2j/persist/RandomAccessQuery.java 2020-09-24 17:51:18 +0000
+++ src/com/goldencode/p2j/persist/RandomAccessQuery.java 2021-01-13 21:04:41 +0000
@@ -4,7 +4,7 @@
**
** Copyright (c) 2004-2020, Golden Code Development Corporation.
**
-** -#- -I- --Date-- --JPRM-- --------------------------Description-------------------------------
+** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20051007 @23259 Created initial version. Backing query for
** converted queries which require a dynamic
** record retrieval semantic.
@@ -328,6 +328,7 @@
** 114 OM 20200915 Handled query navigation on fatal error cases.
** ECF 20200919 Persistence.load API signature change.
** CA 20200924 Replaced Method.invoke with ReflectASM.
+** OM 20201120 Added javadoc note about a possible incorrect logged error.
*/
/*
@@ -1254,8 +1255,7 @@
if (buffer.isReadonly())
{
- // TODO: readonly buffers cannot be used in any FIND statement
- // but can be used in CAN-FIND functions
+ // TODO: readonly buffers cannot be used in a FIND statement but can be used in CAN-FIND functions
}
buffer.initialize();
@@ -1300,7 +1300,9 @@
if (indexName == null && LOG.isLoggable(Level.SEVERE))
{
- LOG.log(Level.SEVERE, String.format(ERROR_LOCATE_SORT_INDEX, sort));
+ // NOTE: sometimes this is not a real warning: at least in findByRowid(), when a sort order is
+ // intentionally manufactured just to get [this] initialized, but index is not really used
+ LOG.log(Level.SEVERE, String.format(ERROR_LOCATE_SORT_INDEX, sort));
}
}
@@ -3380,7 +3382,7 @@
// - the query has an embedded CAN-FIND
// - the query has a client-side where expression
if (ffCache != null &&
- (activeBundleKey == FIRST ||activeBundleKey == LAST || activeBundleKey == UNIQUE) &&
+ (activeBundleKey == FIRST || activeBundleKey == LAST || activeBundleKey == UNIQUE) &&
join == null && getExternalBuffers() == null)
{
key = FastFindCache.createKey(buffer, index, where, activeBundleKey, values);
=== modified file 'src/com/goldencode/p2j/persist/Record.java'
--- src/com/goldencode/p2j/persist/Record.java 2020-09-18 11:12:02 +0000
+++ src/com/goldencode/p2j/persist/Record.java 2021-01-13 21:04:41 +0000
@@ -4,11 +4,12 @@
**
** Copyright (c) 2019-2020, Golden Code Development Corporation.
**
-** -#- -I- --Date-- --------------------------------Description----------------------------------
+** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 ECF 20191001 Created initial version with basic data and accessors.
** OM 20200110 Added support various data types and setter/getter signatures.
** 002 AIL 20200826 Handle getters should use a string to handle conversion without zero default
** for invalid resources.
+** 003 OM 20201213 Added utility method used for debugging.
*/
/*
@@ -1810,7 +1811,6 @@
*/
protected void updateObjectRefCount(object> oldVal, object> newVal)
{
-
}
/**
@@ -1824,39 +1824,68 @@
return _recordMeta();
}
- //@Override
- public String toString2()
+ /**
+ * Utility method used for debugging.
+ *
+ * @param oneLine
+ * Simplify the result to make it fit on a single line. Some details are skipped and column names.
+ *
+ * @return the description of the object as configured by parameter(s).
+ */
+ public String toString2(boolean oneLine)
{
StringBuilder sb = new StringBuilder();
- sb.append("Record(").append(_recordMeta().legacyName).append(":").append(id).append("){\n");
- PropertyMeta[] pms = _recordMeta().getPropertyMeta(false);
- for (int i = 0; i < pms.length; i++)
+
+ RecordMeta meta = _recordMeta();
+ if (oneLine)
{
- if (i != 0)
- {
- sb.append(",\n");
- }
- sb.append('\t').append(pms[i].getLegacyName()).append(": ");
-
- int extent = pms[i].getExtent();
- if (extent == 0)
- {
+ int last = data.length;
+ int start = (this instanceof TempTableRecord) ? 5 : 0;
+ sb.append(meta.legacyName).append('/').append(meta.tables[0]).append(':').append(id).append(" {");
+ for (int i = start; i < last; i++)
+ {
+ if (i > 0)
+ {
+ sb.append(", ");
+ }
+
sb.append(data[i]);
}
- else
+ sb.append('}');
+ }
+ else
+ {
+ sb.append("Record(").append(meta.legacyName).append(":").append(id).append("){\n");
+ PropertyMeta[] pms = meta.getPropertyMeta(false);
+ for (int i = 0; i < pms.length; i++)
{
- for (int k = 0; k < extent; k++)
+ if (i != 0)
{
- sb.append(k == 0 ? "[" : "");
- sb.append(data[i + k]);
+ sb.append(",\n");
}
+ sb.append('\t').append(pms[i].getLegacyName()).append(": ");
- i += extent - 1;
- sb.append("]");
+ int extent = pms[i].getExtent();
+ if (extent == 0)
+ {
+ sb.append(data[i]);
+ }
+ else
+ {
+ for (int k = 0; k < extent; k++)
+ {
+ sb.append(k == 0 ? "[" : "");
+ sb.append(data[i + k]);
+ }
+
+ i += extent - 1;
+ sb.append("]");
+ }
}
+
+ sb.append("\n}");
}
- sb.append("\n}");
return sb.toString();
}
}
=== modified file 'src/com/goldencode/p2j/persist/RecordBuffer.java'
--- src/com/goldencode/p2j/persist/RecordBuffer.java 2020-10-07 16:04:19 +0000
+++ src/com/goldencode/p2j/persist/RecordBuffer.java 2021-01-29 00:53:41 +0000
@@ -2,7 +2,7 @@
** Module : RecordBuffer.java
** Abstract : Encapsulates a data record and provides methods to operate on it
**
-** Copyright (c) 2004-2020, Golden Code Development Corporation.
+** Copyright (c) 2004-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20050816 @22479 Created initial version. Abstract base class
@@ -1079,7 +1079,7 @@
** to validate() (conditional ValidationException throw)
** 320 AIL 20200611 Moved buffer manager's buffer loaded notify after record loading.
** 321 OM 20191101 Used new persistence API.
-** 20200623 Added persistentProcedure member which stores the procedure who
+** 322 OM 20200623 Added persistentProcedure member which stores the procedure who
** opened this buffer for the first time.
** IAS 20200819 Added table CRUD ops statistics support
** GES 20200731 Eliminated context-local lookups by using ErrorHelper.
@@ -1109,6 +1109,16 @@
** CA 20201007 Fixed buffer definitions from internal procedures/functions - the 'defining
** procedure' was incorrectly assumed as the caller, and not the currently executed
** external program.
+** AIL 20201210 Added faster buffer-copy only for buffers which compatible explicit signatures.
+** ECF 20201210 Reworked toString() to use legacy field names.
+** AIL 20201223 Extended support for buffer-copy in fast mode.
+** 20210104 Fixed assign trigger checking when doing fast buffer copy.
+** 20210129 Setting active buffer before doing fast buffer copy.
+** OM 20201120 Added DATA-SOURCE-MODIFIED and DECIMALS implementation. Made methods acessible to
+** [serial] package. Fixed dataset events firing.
+** OM 20201218 Fixed implementation for error and rejected attributes/hidden fields.
+** OM 20210127 Replaced TableMapper.getLegacyName() triple map lookup with direct access to
+** local dmoMeta.legacyTable.
*/
/*
@@ -1166,7 +1176,6 @@
package com.goldencode.p2j.persist;
-import java.beans.*;
import java.lang.InstantiationException;
import java.lang.reflect.*;
import java.sql.*;
@@ -1175,7 +1184,6 @@
import java.util.logging.*;
import com.goldencode.p2j.*;
import com.goldencode.p2j.persist.orm.*;
-import com.goldencode.p2j.persist.orm.Property;
import com.goldencode.p2j.persist.orm.types.DataHandler;
import com.goldencode.p2j.persist.dialect.*;
import com.goldencode.p2j.persist.dirty.*;
@@ -1609,14 +1617,20 @@
private final boolean vst;
/**
- * Flags whether the record stored has been write-touched. That is, whether a setter was
- * invoked on the DMO, regardless whether the value was not actually changed since the last
- * load of the record. Unlike {@code recordChanged} that tracks external changes (from other
- * users), this boolean value tracks write-touches only from current user.
+ * Flags whether the record stored has been write-touched. That is, whether a setter was invoked on the
+ * DMO, regardless whether the value was not actually changed since the last load of the record. Unlike
+ * {@code recordChanged} that tracks external changes (from other users), this boolean value tracks
+ * write-touches only from current user.
*/
private boolean isTouched = false;
/**
+ * Flag used to avoid recursive call while invoking ROW-UPDATE event procedure while another block is
+ * finished.
+ */
+ private boolean firingRowUpdate = false;
+
+ /**
* Collects all {@code Progress.Lang.Object} fields of the record. Only makes sense for temp
* tables. When empty, the OO support (ref-counting) is disabled.
*/
@@ -1625,6 +1639,14 @@
/** Determines if this buffer is dynamic. */
private boolean dynamic = false;
+ /**
+ * Marker for a CREATE-ROW callback in process. When this flag is on, the buffer contains a transient
+ * record but the before image was not yet created for it. In the event of a DELETE operation in a
+ * subsequent callback, the record is simply discarded instead of creating a superfluous delete
+ * before-image for it.
+ */
+ protected boolean createRowCallback = false;
+
/** The query off-end listener associated with this record buffer. */
private final QueryOffEndListener offEndListener = new QueryOffEndListener()
{
@@ -2284,9 +2306,9 @@
{
String schema = DatabaseManager.getSchema(database);
Class extends Record> dmoClass = TableMapper.getDMOClass(schema, tableName);
- Class> dmoIface = DmoMetadataManager.getDMOInterface(dmoClass);
+ DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(dmoClass);
String ldbName = ConnectionManager.get().getLDBName(database);
- dmo = createDynamicBufferForPermTable(dmoIface, actualBufName, ldbName);
+ dmo = createDynamicBufferForPermTable(dmoInfo.getAnnotatedInterface(), actualBufName, ldbName);
}
else
{
@@ -2741,8 +2763,7 @@
else
{
// permanent buffer
- String tableName = TableMapper.getLegacyName((Buffer) srcBuffer.getDMOProxy());
- String actualBufName = bufName != null ? bufName.getValue() : tableName;
+ String actualBufName = bufName != null ? bufName.getValue() : srcBuffer.dmoInfo.legacyTable;
dmo = createDynamicBufferForPermTable(srcBuffer.getDMOInterface(),
actualBufName,
@@ -3194,7 +3215,7 @@
DatabaseTriggerManager.handleError(
1003,
DatabaseEventType.ASSIGN,
- TableMapper.getLegacyName(dmoIface),
+ buffer.dmoInfo.legacyTable,
TableMapper.getLegacyFieldName(dmoIface, prop));
}
@@ -3903,6 +3924,17 @@
dstRec = dstBuf.getCurrentRecord();
}
+ boolean hasBefore = ((BufferImpl) srcProxy).isAfterBuffer() &&
+ ((BufferImpl) dstProxy).isAfterBuffer() &&
+ srcBuf.tableHandle().unwrapTempTable().isTrackingChanges().booleanValue();
+
+ // try a fast copy first
+ // if it worked, then we are done with the copy all together
+ if (fastCopy(srcBuf, dstBuf, srcFields != null && srcFields.length > 0, noLobs, validate, hasBefore))
+ {
+ return;
+ }
+
Map srcLegacyFields = srcBuf.getLegacyFieldNameMap().getLegacyField2Name();
Map dstLegacyFields = dstBuf.getLegacyFieldNameMap().getLegacyField2Name();
@@ -3929,33 +3961,28 @@
OperationType.COPY);
Map copyB4Map = null;
- if (((BufferImpl) srcProxy).isAfterBuffer() && ((BufferImpl) dstProxy).isAfterBuffer())
+ if (hasBefore)
{
- boolean before = srcBuf.tableHandle().unwrapTempTable().isTrackingChanges().booleanValue();
+ BufferImpl srcB4Proxy = (BufferImpl) ((BufferImpl) srcProxy).beforeBuffer().getResource();
+ BufferImpl dstB4Proxy = (BufferImpl) ((BufferImpl) dstProxy).beforeBuffer().getResource();
+ RecordBuffer srcB4Buf = ((BufferReference) srcB4Proxy).buffer();
+ RecordBuffer dstB4Buf = ((BufferReference) dstB4Proxy).buffer();
- if (before)
- {
- BufferImpl srcB4Proxy = (BufferImpl) ((BufferImpl) srcProxy).beforeBuffer().getResource();
- BufferImpl dstB4Proxy = (BufferImpl) ((BufferImpl) dstProxy).beforeBuffer().getResource();
- RecordBuffer srcB4Buf = ((BufferReference) srcB4Proxy).buffer();
- RecordBuffer dstB4Buf = ((BufferReference) dstB4Proxy).buffer();
-
- copyB4Map = createSimplePropsMap(lFields,
- srcB4Buf,
- dstB4Buf,
- srcB4Buf.getLegacyGetterMap(),
- dstB4Buf.getLegacySetterMap(),
- srcB4Buf.getExtentMap(),
- dstB4Buf.getExtentMap(),
- srcLegacyFields,
- dstLegacyFields,
- srcB4Buf.getPropsByGetter(),
- dstB4Buf.getPropsBySetter(),
- srcB4Buf.getGetterDatums(),
- dstB4Buf.getSetterDatums(),
- noLobs,
- OperationType.COPY);
- }
+ copyB4Map = createSimplePropsMap(lFields,
+ srcB4Buf,
+ dstB4Buf,
+ srcB4Buf.getLegacyGetterMap(),
+ dstB4Buf.getLegacySetterMap(),
+ srcB4Buf.getExtentMap(),
+ dstB4Buf.getExtentMap(),
+ srcLegacyFields,
+ dstLegacyFields,
+ srcB4Buf.getPropsByGetter(),
+ dstB4Buf.getPropsBySetter(),
+ srcB4Buf.getGetterDatums(),
+ dstB4Buf.getSetterDatums(),
+ noLobs,
+ OperationType.COPY);
}
copy(srcBuf, dstBuf, copyMap, copyB4Map, validate);
@@ -4113,29 +4140,6 @@
}
/**
- * Copy the content of a DMO to another. Only matching fields are processed. Only the fields
- * are copied, the special and surrogate data is not copied.
- *
- * @param dst
- * The target DMO.
- * @param src
- * The source DMO.
- * @param dstBuffer
- * The destination buffer. Only used to get information on the legacy fields (their
- * order and type).
- * @param srcBuffer
- * The source buffer. Only used to get information on the legacy fields (their
- * order and type).
- *
- * @return {@code true} if operation is successful, and {@code false} on error (if the fields
- * types are not matching).
- */
- static boolean copyDMO(Record dst, Record src, BufferImpl dstBuffer, BufferImpl srcBuffer)
- {
- return BaseRecord.copy(src, dst);
- }
-
- /**
* BUFFER-COPY() handle-based method runtime.
*
* This method copies any common fields, determined by name, data type, and extent-matching,
@@ -4180,8 +4184,23 @@
dstRec = dstBuf.getCurrentRecord();
}
+ BufferReference srcProxy = srcBuf.getDMOProxy();
+ BufferReference dstProxy = dstBuf.getDMOProxy();
+ boolean hasBefore = ((BufferImpl) srcProxy).isAfterBuffer() &&
+ ((BufferImpl) dstProxy).isAfterBuffer() &&
+ srcBuf.tableHandle().unwrapTempTable().isTrackingChanges().booleanValue();
+ boolean hasPairs = !(pairs == null || pairs.isUnknown() || pairs.getValue().length() == 0);
+ boolean hasExcept = !(except == null || except.isUnknown() || except.getValue().length() == 0);
+
try
{
+ // try a fast copy first
+ // if it worked, then we are done with the copy all together
+ if (fastCopy(srcBuf, dstBuf, hasPairs || hasExcept, noLobs.getValue(), true, hasBefore))
+ {
+ return new logical(true);
+ }
+
Map dstSetters = dstBuf.getLegacySetterMap();
Map propsMap = getPropsMap(srcBuf,
dstBuf,
@@ -4196,32 +4215,25 @@
{
Map propsB4Map = null;
- BufferReference srcProxy = srcBuf.getDMOProxy();
- BufferReference dstProxy = dstBuf.getDMOProxy();
- if (((BufferImpl) srcProxy).isAfterBuffer() && ((BufferImpl) dstProxy).isAfterBuffer())
+ if (hasBefore)
{
- boolean before = srcBuf.tableHandle().unwrapTempTable().isTrackingChanges().booleanValue();
+ BufferImpl srcB4Proxy = (BufferImpl) ((BufferImpl) srcProxy).beforeBuffer().getResource();
+ BufferImpl dstB4Proxy = (BufferImpl) ((BufferImpl) dstProxy).beforeBuffer().getResource();
+ RecordBuffer srcB4Buf = (TemporaryBuffer) ((BufferReference) srcB4Proxy).buffer();
+ RecordBuffer dstB4Buf = (TemporaryBuffer) ((BufferReference) dstB4Proxy).buffer();
+ Map dstB4Setters = dstB4Buf.getLegacySetterMap();
- if (before)
- {
- BufferImpl srcB4Proxy = (BufferImpl) ((BufferImpl) srcProxy).beforeBuffer().getResource();
- BufferImpl dstB4Proxy = (BufferImpl) ((BufferImpl) dstProxy).beforeBuffer().getResource();
- RecordBuffer srcB4Buf = (TemporaryBuffer) ((BufferReference) srcB4Proxy).buffer();
- RecordBuffer dstB4Buf = (TemporaryBuffer) ((BufferReference) dstB4Proxy).buffer();
- Map dstB4Setters = dstB4Buf.getLegacySetterMap();
-
- propsB4Map = getPropsMap(srcB4Buf,
- dstB4Buf,
- dstB4Setters,
- dstB4Buf.getPropsBySetter(),
- dstB4Buf.getSetterDatums(),
- except,
- pairs,
- noLobs,
- OperationType.COPY);
- }
+ propsB4Map = getPropsMap(srcB4Buf,
+ dstB4Buf,
+ dstB4Setters,
+ dstB4Buf.getPropsBySetter(),
+ dstB4Buf.getSetterDatums(),
+ except,
+ pairs,
+ noLobs,
+ OperationType.COPY);
}
-
+
return copy(srcBuf, dstBuf, propsMap, propsB4Map, true);
}
}
@@ -5521,6 +5533,184 @@
}
/**
+ * Fast way to do the buffer-copy. This doesn't work in all scenarios, but can greatly fasten
+ * some cases (which are often in practice). This will check if the current scenario is trivial:
+ * no assign triggers, no before buffers, no explicit pair copy and explicit dmo signature match.
+ * For this case, the fields are set in bulk through direct access.
+ *
+ * @param srcBuf
+ * The source buffer from which the copy is done.
+ * @param dstBuf
+ * The destination buffer to which the copy is done.
+ * @param forcePair
+ * Flag to indicate if an explicit pair of fields should be copied / not copied.
+ * @param noLobs
+ * Flag to indicate if lobs should be omitted from the copy.
+ * @param validate
+ * {@code true} to validate the destination buffer after the copy.
+ * @param hasBefore
+ * Flag to indicate if the buffer have before buffer which should be also copied.
+ *
+ * @return {@code true} if the fast copy is eligible and could be done.
+ * {@code false} if the fast copy couldn't be applied for these buffers.
+ */
+ private static boolean fastCopy(RecordBuffer srcBuf,
+ RecordBuffer dstBuf,
+ boolean forcePair,
+ boolean noLobs,
+ boolean validate,
+ boolean hasBefore)
+ {
+ Record srcRec = srcBuf.getCurrentRecord();
+ Record dstRec = dstBuf.getCurrentRecord();
+ DmoMeta srcMeta = srcBuf.getDmoInfo();
+ DmoMeta dstMeta = dstBuf.getDmoInfo();
+ DmoSignature srcSignature = srcMeta.getSignature();
+ DmoSignature dstSignature = dstMeta.getSignature();
+
+ Iterator dstPropIt = dstBuf.getDmoInfo().getFields(false);
+ Set dstProps = new HashSet<>();
+ while (dstPropIt.hasNext())
+ {
+ Property prop = dstPropIt.next();
+ dstProps.add(prop.name);
+ }
+
+ if (!noLobs && !forcePair && !hasBefore &&
+ !dstBuf.triggerTracker.hasAnyAssignTrigger(dstProps) &&
+ DMOSignatureHelper.validBufferCopy(srcSignature, dstSignature))
+ {
+ // all the field names are both in the source and the destination
+ // at this point the DMOs are similar enough to fasten the copy
+ boolean success = true;
+
+ if (DMOSignatureHelper.exactPropertyOrder(srcSignature, dstSignature))
+ {
+ // the fields are also in the same order, which means we can assign directly the values
+ // from the source to the destination
+ boolean batchError = true;
+ boolean error = false;
+ boolean inBatch = dstBuf.getBufferManager().isBatchAssignMode();
+
+ try
+ {
+ if (validate)
+ {
+ startBatch(true);
+ }
+
+ dstRec.setActiveBuffer(dstBuf);
+ try
+ {
+ dstBuf.bulkCopy = true;
+ success = success && OrmUtils.setAllFields(srcRec, dstRec);
+ batchError = false;
+ }
+ finally
+ {
+ // make sure reset is called first as endBatch can throw an exception
+ dstRec.resetActiveState();
+
+ if (validate)
+ {
+ endBatch(batchError);
+ }
+ }
+
+ if (success)
+ {
+ return true;
+ }
+ else
+ {
+ // otherwise, fallback to the classic implementation
+ if (LOG.isLoggable(Level.SEVERE))
+ {
+ LOG.log(Level.SEVERE,
+ "Explicit signature checking is broken; unsuccessful fast buffer copy. " +
+ "Fallback to classic implementation.");
+ }
+ return false;
+ }
+ }
+ catch (ConditionException exc)
+ {
+ error = true;
+ throw exc;
+ }
+ catch (Exception exc)
+ {
+ error = true;
+
+ DBUtils.handleException(srcBuf.getDatabase(), exc);
+ DBUtils.handleException(dstBuf.getDatabase(), exc);
+
+ String msg = "Error performing buffer copy";
+ if (LOG.isLoggable(Level.SEVERE))
+ {
+ LOG.log(Level.SEVERE, msg, exc);
+ }
+
+ // TODO: use proper error.
+ ErrorManager.recordOrThrowError(-1, msg);
+ return false;
+ }
+ finally
+ {
+ dstBuf.bulkCopy = false;
+
+ if (dstBuf.isCommitPending() && !inBatch)
+ {
+ try
+ {
+ if (!error)
+ {
+ dstBuf.getPersistence().commit(dstBuf.getPersistenceContext());
+ }
+ else
+ {
+ dstBuf.getPersistence().rollback(dstBuf.getPersistenceContext());
+ }
+ }
+ catch (Exception exc)
+ {
+ DBUtils.handleException(srcBuf.getDatabase(), exc);
+ DBUtils.handleException(dstBuf.getDatabase(), exc);
+
+ String msg = "Error " +
+ (error ? "rolling back" : "comitting") +
+ " during buffer copy";
+ if (LOG.isLoggable(Level.SEVERE))
+ {
+ LOG.log(Level.SEVERE, msg, exc);
+ }
+ }
+ finally
+ {
+ dstBuf.setCommitPending(false);
+ }
+ }
+ }
+ }
+ else
+ {
+ // TODO: implement a faster copy in case the signatures match but the field order
+ // doesn't match
+ if (LOG.isLoggable(Level.WARNING))
+ {
+ LOG.log(Level.WARNING,
+ "Unimplememted fast copy for explicit signature match, but different field order." +
+ "Fallback to classic implementation.");
+ }
+ return false;
+ }
+ }
+
+ // we don't have proper conditions for fast-copy
+ return false;
+ }
+
+ /**
* Get the {@link #undoable} instance.
*
* @return See above.
@@ -5713,7 +5903,7 @@
// at a sub-transaction boundary, validate and flush a new or changed record
// Note: for a new record, this means default values will be saved in any fields which have not
- // otherwise been explicitly updated
+ // otherwise been explicitly updated
boolean global = registeredWithGlobalScope == 0;
int blockDepth = bufferManager.getCurrentScope();
@@ -5780,6 +5970,13 @@
return;
}
+ if (transaction && isTouched && !firingRowUpdate)
+ {
+ firingRowUpdate = true;
+ maybeFireRowUpdateEvent();
+ isTouched = false; // prevent firing ROW-UPDATE a second time when record is released
+ firingRowUpdate = false;
+ }
int blockDepth = bufferManager.getCurrentScope();
triggerTracker.commit(blockDepth);
}
@@ -6555,10 +6752,6 @@
{
ErrorManager.recordOrThrowError(exc);
}
- finally
- {
- isTouched = false;
- }
}
/**
@@ -6649,6 +6842,17 @@
}
/**
+ * Constructs and returns a string containing the P4GL schema definition of the table. The result is not
+ * cached so this method is to be used only for debug.
+ *
+ * @return a string containing the P4GL legacy schema definition of the table.
+ */
+ public String tableDefinition()
+ {
+ return dmoInfo.getTableDefinition();
+ }
+
+ /**
* Get the DMO proxy which references this buffer instance.
*
* @return DMO proxy object.
@@ -6959,10 +7163,10 @@
*/
protected String getFieldHelp(String property)
{
- return (String) getFieldValue(property,
- fieldHelp,
- RecordBuffer::getFieldHelp,
- TableMapper::getLegacyFieldHelp);
+ return getFieldValue(property,
+ fieldHelp,
+ RecordBuffer::getFieldHelp,
+ TableMapper::getLegacyFieldHelp);
}
/**
@@ -6975,10 +7179,10 @@
*/
protected String getFieldColumnLabel(String property)
{
- return (String) getFieldValue(property,
- fieldColumnLabels,
- RecordBuffer::getFieldColumnLabel,
- TableMapper::getLegacyFieldColumnLabel);
+ return getFieldValue(property,
+ fieldColumnLabels,
+ RecordBuffer::getFieldColumnLabel,
+ TableMapper::getLegacyFieldColumnLabel);
}
/**
@@ -7040,10 +7244,10 @@
*/
protected String getFieldLabel(String property)
{
- return (String) getFieldValue(property,
- fieldLabels,
- RecordBuffer::getFieldLabel,
- TableMapper::getLegacyFieldLabel);
+ return getFieldValue(property,
+ fieldLabels,
+ RecordBuffer::getFieldLabel,
+ TableMapper::getLegacyFieldLabel);
}
/**
@@ -7074,10 +7278,10 @@
*/
protected String getFieldValidateExpression(String property)
{
- return (String) getFieldValue(property,
- fieldValidateExpressions,
- RecordBuffer::getFieldValidateExpression,
- TableMapper::getLegacyFieldValidateExpression);
+ return getFieldValue(property,
+ fieldValidateExpressions,
+ RecordBuffer::getFieldValidateExpression,
+ TableMapper::getLegacyFieldValidateExpression);
}
/**
@@ -7108,10 +7312,10 @@
*/
protected String getFieldValidateMessage(String property)
{
- return (String) getFieldValue(property,
- fieldValidateMessages,
- RecordBuffer::getFieldValidateMessage,
- TableMapper::getLegacyFieldValidateMessage);
+ return getFieldValue(property,
+ fieldValidateMessages,
+ RecordBuffer::getFieldValidateMessage,
+ TableMapper::getLegacyFieldValidateMessage);
}
/**
@@ -7142,10 +7346,10 @@
*/
protected String getFieldFormat(String property)
{
- return (String) getFieldValue(property,
- fieldFormats,
- RecordBuffer::getFieldFormat,
- TableMapper::getLegacyFieldFormat);
+ return getFieldValue(property,
+ fieldFormats,
+ RecordBuffer::getFieldFormat,
+ TableMapper::getLegacyFieldFormat);
}
/**
@@ -7176,10 +7380,10 @@
*/
protected logical getFieldLiteralQuestion(String property)
{
- return (logical) getFieldValue(property,
- fieldLiteralQuestions,
- RecordBuffer::getFieldLiteralQuestion,
- TableMapper::getLegacyFieldLiteralQuestion);
+ return getFieldValue(property,
+ fieldLiteralQuestions,
+ RecordBuffer::getFieldLiteralQuestion,
+ TableMapper::getLegacyFieldLiteralQuestion);
}
/**
@@ -7207,7 +7411,6 @@
protected void rowState(Integer state)
{
// not a temp-table record
- return;
}
/**
@@ -7228,36 +7431,51 @@
* is the {@code after-rowid} and {@code before-rowid} for AFTER-TABLE record.
*
* @param peer
- * The new long rowid value of the peer record or {@code null}.
+ * The new rowid value of the peer record or {@code null}.
*/
protected void peerRowid(rowid peer)
{
// not a temp-table record
- return;
+ }
+
+ /**
+ * Sets the {@code origin-rowid} for AFTER-TABLE record.
+ *
+ * @param peer
+ * The new rowid value of the record or {@code null}.
+ */
+ public void originRowid(rowid peer)
+ {
+ // not a temp-table record
}
/**
* Gets {@code errorFlag} of this record.
*
- * @return Since this method is invoked for permanent records this will always return
- * {@code unknown} value.
+ * @return the {@code errorFlag} of this record or {@code null} if it was not configured. If not null,
+ * the value is a bitwise combination of ERROR and REJECTED attribute. It's up to the called to
+ * extract the bit(s) it needs.
+ * Since this method is invoked for permanent records this will always return {@code unknown}
+ * value.
*/
- protected integer errorFlag()
+ protected Integer errorFlags()
{
// not a temp-table record
- return new integer();
+ return null;
}
/**
- * Sets the {@code errorFlag} value this peer record.
+ * Sets or removes the {@code errorFlag} value this peer record. The {@code error} attribute of the record
+ * is composed of multiple bit flags. This method manages its value based on the parameters it receives.
*
- * @param err
- * The new error flag.
+ * @param errFlag
+ * The error bit to be set or removed.
+ * @param set
+ * Use {@code true} to set the flag and {@code false} to remove it.
*/
- protected void errorFlag(integer err)
+ protected void updateErrorFlags(int errFlag, boolean set)
{
// not a temp-table record
- return;
}
/**
@@ -7281,7 +7499,6 @@
protected void errorString(Text string)
{
// not a temp-table record
- return;
}
/**
@@ -7334,26 +7551,26 @@
* buffer-specific value, it is returned. Then, for temp-tables, buffer-specific value of the
* default buffer if returned. Then the value specified in schema is returned.
*
- * @param property
- * Property name of the target buffer field.
- * @param bufFieldVals
- * Map of the buffer-specific attribute values for the fields of this buffer.
- * @param defaultBufValGetter
- * Function for getting buffer-specific attribute values for the fields of the default
- * buffer (for temp tables only).
- * @param schemaValGetter
- * Function for getting attribute values defined in schema.
+ * @param property
+ * Property name of the target buffer field.
+ * @param bufFieldVals
+ * Map of the buffer-specific attribute values for the fields of this buffer.
+ * @param defaultBufValGetter
+ * Function for getting buffer-specific attribute values for the fields of the default
+ * buffer (for temp tables only).
+ * @param schemaValGetter
+ * Function for getting attribute values defined in schema.
*
* @return value of the target attribute for the given buffer field.
*/
- protected Object getFieldValue(String property,
- Map bufFieldVals,
+ protected T getFieldValue(String property,
+ Map bufFieldVals,
BiFunction defaultBufValGetter,
BiFunction schemaValGetter)
{
if (bufFieldVals != null)
{
- Object res = bufFieldVals.get(property);
+ T res = bufFieldVals.get(property);
if (res != null)
{
return res;
@@ -7365,11 +7582,11 @@
RecordBuffer defaultBuffer = getDefaultBuffer();
if (!RecordBuffer.this.equals(defaultBuffer))
{
- return defaultBufValGetter.apply(defaultBuffer, property);
+ return (T) defaultBufValGetter.apply(defaultBuffer, property);
}
}
- return schemaValGetter.apply((Buffer) getDMOProxy(), property);
+ return (T) schemaValGetter.apply((Buffer) getDMOProxy(), property);
}
/**
@@ -7699,7 +7916,7 @@
*
* @return null.
*/
- protected Integer getMultiplexID()
+ public Integer getMultiplexID()
{
return null;
}
@@ -7870,8 +8087,7 @@
if (topEvent != null)
{
// cannot fire DELETE trigger while any other trigger is executing
- String legacyTable = TableMapper.getLegacyName(dmoInterface);
- DatabaseTriggerManager.handleError(3169, topEvent, legacyTable, null);
+ DatabaseTriggerManager.handleError(3169, topEvent, dmoInfo.legacyTable, null);
}
else
{
@@ -7994,11 +8210,11 @@
* parameters.
*
* This method is invalid in base class so it will always throw an exception. It must be
- * overwritten by {@link TemporaryBuffer#delete} with correct logic.
+ * overwritten by {@link TemporaryBuffer#delete} with correct logic.
*
* @param suppDMOs
* The DMOs for the external (additional) buffers that are accessed in inner
- * subselect, or {@code null} in case of a simple {@code where} predicate.
+ * subselect, or {@code null} in case of a simple {@code where} predicate.
* @param where
* An HQL where clause snippet which defines the restriction criteria to apply to the
* delete. All references to properties in a DMO must be unqualified.
@@ -8008,7 +8224,7 @@
* @throws PersistenceException
* always.
*/
- protected void delete(DataModelObject[] suppDMOs, String where, Object[] args)
+ protected void delete(DataModelObject[] suppDMOs, String where, Object... args)
throws PersistenceException
{
if (suppDMOs != null)
@@ -8038,7 +8254,7 @@
* @throws PersistenceException
* if an error occurs performing the bulk delete operation.
*/
- protected void delete(String where, Object[] args)
+ protected void delete(String where, Object... args)
throws PersistenceException
{
release(true);
@@ -8401,11 +8617,7 @@
*/
protected int lookupNumFields()
{
- Class extends DataModelObject> dmoIface = getDMOInterface();
- String schema = getSchema();
- String legacyName = TableMapper.getLegacyName(dmoIface);
-
- return TableMapper.getNumFields(schema, legacyName);
+ return TableMapper.getNumFields(getSchema(), dmoInfo.legacyTable);
}
/**
@@ -8843,66 +9055,42 @@
return buf.toString();
}
- String name = null;
- try
+ if (metadata != null)
{
- Map getters = new LinkedHashMap<>();
+ String name = null;
+ PropertyMeta[] propMeta = metadata.getPropertyMeta(false);
+ int len = propMeta.length;
+ String[] names = new String[len];
int maxWidth = 0;
- Method[] methods = dmoClass.getDeclaredMethods();
- int len = methods.length;
for (int i = 0; i < len; i++)
{
- Method next = methods[i];
- name = next.getName();
- boolean prefixGet = name.startsWith("get");
- boolean prefixIs = name.startsWith("is");
- if ((prefixGet || prefixIs) &&
- next.getParameterTypes().length == 0 &&
- !Collection.class.isAssignableFrom(next.getReturnType()))
+ PropertyMeta next = propMeta[i];
+ name = next.getLegacyName();
+ int extent = next.getExtent();
+ if (extent > 0)
{
- name = name.substring(prefixGet ? 3 : 2);
- name = Introspector.decapitalize(name);
- getters.put(name, next);
- maxWidth = Math.max(maxWidth, name.length());
+ name = name + '[' + extent + ']';
}
+ names[i] = name;
+ maxWidth = Math.max(maxWidth, name.length());
}
buf.append(" ");
- StringHelper.leftAlignText(DatabaseManager.PRIMARY_KEY, maxWidth, buf);
+ StringHelper.leftAlignText(Session.PK, maxWidth, buf);
buf.append(" : ");
buf.append(record.primaryKey());
- for (Map.Entry entry : getters.entrySet())
+ Object[] data = currentRecord.getData(this);
+
+ for (int i = 0; i < len; i++)
{
- name = entry.getKey();
- Method next = entry.getValue();
+ name = names[i];
buf.append(sep);
buf.append(" ");
StringHelper.leftAlignText(name, maxWidth, buf);
buf.append(" : ");
- try
- {
- Object value = next.invoke(record, (Object[]) null);
- buf.append(formatProperty(value));
- }
- catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
- {
- // eat it
- }
- }
- }
- catch (Exception exc)
- {
- DBUtils.handleException(getDatabase(), exc);
-
- if (LOG.isLoggable(Level.WARNING))
- {
- LOG.log(Level.WARNING,
- describe() + ": error dumping record contents --> " +
- getDMOName() + ":" + record.primaryKey() + " at property '" +
- name + "'",
- exc);
+ buf.append(formatProperty(data[i]));
}
}
@@ -9226,7 +9414,7 @@
{
if (currentRecord != null)
{
- setCurrentRecord(null, false, false, false, allowWriteTrigger);
+ setCurrentRecord(null, undo, false, false, allowWriteTrigger);
}
}
@@ -9387,8 +9575,7 @@
{
if (triggerTracker.isExecuting(det, recId))
{
- String legacyTable = TableMapper.getLegacyName(dmoInterface);
- DatabaseTriggerManager.handleError(2873, det, legacyTable, null);
+ DatabaseTriggerManager.handleError(2873, det, dmoInfo.legacyTable, null);
}
else
{
@@ -9677,7 +9864,7 @@
*
* @return Persistence service object for this buffer's database.
*/
- protected Persistence getPersistence()
+ public Persistence getPersistence()
{
checkActive();
@@ -9897,13 +10084,43 @@
// the record is still transient.
createScope = bufferManager.getCurrentScope();
+ // invoke callback procedures first. Any trigger procedure is executed after this event is handled
+ if (isTemporary())
+ {
+ if (buf._dataSet() != null)
+ {
+ // checks whether tracking is active
+ if (getParentTable().isTrackingChanges().toJavaType())
+ {
+ try
+ {
+ createRowCallback = true;
+ if (!buf.invokeCallback(Buffer.ROW_CREATE_EVENT))
+ {
+ BlockManager.returnError();
+ }
+ }
+ finally
+ {
+ createRowCallback = false;
+ }
+
+ if (currentRecord == null)
+ {
+ // the callback procedure/method deleted the newly created transient record
+ // nothing more to do, just return
+ return;
+ }
+ }
+ }
+ }
+
DatabaseEventType det = DatabaseEventType.CREATE;
if (triggerTracker.isTriggerEnabled(det, null))
{
if (triggerTracker.isExecuting(det, recId))
{
- String legacyTable = TableMapper.getLegacyName(dmoInterface);
- DatabaseTriggerManager.handleError(3168, det, legacyTable, null);
+ DatabaseTriggerManager.handleError(3168, det, dmoInfo.legacyTable, null);
}
else
{
@@ -9951,9 +10168,9 @@
// if this is buffer of an active AFTER-BUFFER, update the BEFORE-TABLE, too:
BufferImpl after = (BufferImpl) dmoProxy;
- if (!ignoreBeforeTracking &&
- after.isAfterBuffer() &&
- !after.dataSet().isUnknown() &&
+ if (!ignoreBeforeTracking &&
+ after.isAfterBuffer() &&
+ after._dataSet() != null &&
TempTableBuilder.asTempTable((Temporary) after).isTrackingChanges().getValue())
{
handle hBefore = after.beforeBuffer();
@@ -10020,7 +10237,7 @@
* @throws ErrorConditionException
* if the buffer fails validation.
*/
- protected boolean validate(boolean throwValidationException)
+ public boolean validate(boolean throwValidationException)
throws ValidationException
{
if (!isAvailable())
@@ -10071,8 +10288,7 @@
* @throws ValidationException
* if the DMO state fails any unique or non-nullable constraint check.
* @throws PersistenceException
- * if there is any error gathering index metadata or a database
- * error performing validation.
+ * if there is any error gathering index metadata or a database error performing validation.
*/
void validate(Record dmo)
throws ValidationException,
@@ -10573,6 +10789,30 @@
}
/**
+ * Set the DMO implementation instance which backs this buffer with a another record, loaded from database.
+ * The buffer will help updating the new record, usually for internal, more complex processing.
+ *
+ * This is a dedicated method accessible only from a very specific places within the persistence package.
+ * When the new record is set, the one eventually existing is released.
+ *
+ * Note that the triggers are NOT fired when using this method.
+ *
+ * @param newCrtRecord
+ * DMO instance which represents the record currently backing this buffer. If {@code null}, the
+ * record (and possibly its corresponding lock) is released.
+ *
+ * @throws ErrorConditionException
+ * if there is an error transitioning a lock when releasing the current record; if there is an
+ * error validating or saving a transient record during flush.
+ *
+ * @see #setCurrentRecord(Record, boolean, boolean, boolean, boolean)
+ */
+ void loadRecord(Record newCrtRecord)
+ {
+ setCurrentRecord(newCrtRecord, false, false, false, false);
+ }
+
+ /**
* Creates a new {@link Validation} instance using the passed arguments, and calls
* {@link Validation#validateMaybeFlush()}. Depending on the {@link Validation#wasFlushed()}
* state, it will {@link #reportChange report} the change so that listeners will be notified.
@@ -11267,6 +11507,12 @@
boolean dirtyCopy,
boolean allowWriteTrigger)
{
+ if (dmoProxy != null)
+ {
+ // reset the DATA-SOURCE-MODIFIED
+ ((Buffer) dmoProxy).setDataSourceModified(false);
+ }
+
if (newCrtRecord != null)
{
getTxHelper().checkTransaction(persistence.getDatabase().getId());
@@ -11306,6 +11552,13 @@
}
}
+ // the ROW-UPDATE event is fired "immediately before the record is updated in the temp-tables"
+ // NOTE: because the way FWD handles transactions that moment is difficult to intercept. When a change
+ // operation occurred on the record, our validation has a secondary effect of flushing the
+ // record to database so at this moment the record is already updated and its flags set to CACHED
+ // and CHANGED flag is off. To detect if the record was indeed changed we test [isTouched] flag
+ maybeFireRowUpdateEvent();
+
boolean clearValidationState = true;
try
{
@@ -11388,8 +11641,7 @@
if (this.currentRecord != null)
{
- // store placeholder record if releasing current record and we
- // haven't done so previously
+ // store placeholder record if releasing current record and we haven't done so previously
if (snapshot == null && (newCrtRecord == null || newlyCreated))
{
if (undoable != null)
@@ -11423,7 +11675,7 @@
else
{
// try to release the lock
- RecordIdentifier ident = this.currentRecord.getRecordLockIdentifier();
+ RecordIdentifier ident = this.currentRecord.getRecordLockIdentifier();
lockContext.lock(ident, LockType.NONE, 0L, this);
}
}
@@ -11464,9 +11716,7 @@
{
if (LOG.isLoggable(Level.WARNING))
{
- LOG.log(Level.WARNING,
- "Error detaching record from session: " + toString(),
- exc);
+ LOG.log(Level.WARNING, "Error detaching record from session: " + toString(), exc);
}
ErrorManager.recordOrThrowError(exc);
@@ -11480,6 +11730,7 @@
{
this.undoable.checkUndoable(true);
}
+
this.currentRecord = newCrtRecord;
this.recordChanged = false; // invalidate CURRENT-CHANGED flag (see above)
this.isTouched = false;
@@ -11521,6 +11772,27 @@
}
/**
+ * Checks whether the conditions for firing ROW-UPDATE event are met. If affirmative the event is triggered
+ * and the dataset's callback is invoked with a handle to this buffer as parameter.
+ */
+ private void maybeFireRowUpdateEvent()
+ {
+ if (isTouched && isTemporary() && this.currentRecord != null)
+ {
+ BufferImpl buf = (BufferImpl) dmoProxy;
+ if (buf._dataSet() != null)
+ {
+ // checks whether tracking is active
+ if (getParentTable().isTrackingChanges().toJavaType())
+ {
+ buf.invokeCallback(Buffer.ROW_UPDATE_EVENT);
+ ErrorManager.clearPending(); // if an error is raised in this callback it is simply ignored
+ }
+ }
+ }
+ }
+
+ /**
* Store the undoability of this buffer in the record's state. Even though the same record
* may be stored in multiple buffers at the same time, this is safe because (1) a record
* instance is unique to a user session, and (2) buffers backed by the same table must have
@@ -11785,7 +12057,7 @@
}
// TODO: use something more reliable (but efficient) here to determine whether this is a setter
- isSetter = Void.TYPE.equals(method.getReturnType());
+ isSetter = (method.getReturnType() == Void.TYPE);
// if another buffer created the current record, delegate setter method calls to it,
// to ensure all validation and flushing is tracked properly
@@ -11873,6 +12145,7 @@
}
}
+ boolean denormalized = false;
if (isSetter)
{
pm = propsBySetter.get(method);
@@ -11889,6 +12162,7 @@
{
int extIdx = (int) args[0]; // already computed as 0-base
pm = metadata.getPropertyMeta(false)[pm.getOffset() + extIdx];
+ denormalized = true;
}
property = pm.getName();
@@ -11955,9 +12229,9 @@
// has a BEFORE-TABLE to keep the changes in and tracking is activated (not in
// FILL mode)
BufferImpl after = (BufferImpl) RecordBuffer.this.dmoProxy;
- if (!ignoreBeforeTracking &&
- after.isAfterBuffer() &&
- !after.dataSet().isUnknown() &&
+ if (!ignoreBeforeTracking &&
+ after.isAfterBuffer() &&
+ after._dataSet() != null &&
TempTableBuilder.asTempTable((Temporary) after).isTrackingChanges().getValue())
{
handle hBefore = after.beforeBuffer();
@@ -11979,6 +12253,7 @@
// keep the reference to the datasource-rowid,
((TemporaryBuffer) before.buffer()).getCurrentRecord()._originRowid(
((TemporaryBuffer) after.buffer()).getCurrentRecord()._originRowid());
+ before.buffer().flush();
before.setUpBeforeBuffer(false);
// do not release the before buffer, let it hold the newly created row
}
@@ -11996,6 +12271,8 @@
if (isSetter)
{
+ isTouched = true;
+
// share dirty if needed
if (currentRecord!= null && isDirtyRead && dirtyContext != null && !isTemporary())
{
@@ -12046,7 +12323,10 @@
changed = diffs != null;
if (changed)
{
- addUnreportedChanges(ormIndex, extent ? (Integer) args[0] : 0, diffs);
+ // NOTE: if the property was denormalized, the property index [ormIndex] already contains
+ // the extent index
+ int extentIndex = extent && !denormalized ? (Integer) args[0] : 0;
+ addUnreportedChanges(ormIndex, extentIndex, diffs);
}
}
@@ -12071,7 +12351,7 @@
DatabaseTriggerManager.handleError(
1003,
DatabaseEventType.ASSIGN,
- TableMapper.getLegacyName(dmoIface),
+ dmoInfo.legacyTable,
TableMapper.getLegacyFieldName(dmoIface, property));
}
}
@@ -13032,25 +13312,6 @@
}
/**
- * A Reversible version which affects more than one record.
- */
- protected abstract class AbstractBulkReversible
- extends Reversible
- {
- /**
- * This method checks if this bulk reversible touched the record with
- * the given key.
- *
- * @param key
- * The record to be checked.
- *
- * @return true if the record was touched by this bulk
- * reversible.
- */
- protected abstract boolean containsRecord(Long key);
- }
-
- /**
* A lightweight implementation of the Undoable interface
* which restores to the record buffer the current record and snapshot
* which were active at the time of backup.
@@ -13268,12 +13529,12 @@
* @param propertyName
* Property name.
* @param extentIndex
- * Index of an element in an extent property or null. If present, the index should
+ * Index of an element in an extent property or {@code null}. If present, the index should
* be zero-based.
* @param accessor
* The cached accessor for this instance.
* @param extent
- * The extent value of this property or null.
+ * The extent value of this property or {@code null}.
* @param propertyMeta
* The cached property meta.
* @param indexed
@@ -13351,15 +13612,21 @@
DatumAccess that = (DatumAccess) o;
- if (extentIndex != null ? !extentIndex.equals(that.extentIndex)
- : that.extentIndex != null)
- {
+ if ((this.extentIndex == null) != (that.extentIndex == null))
+ {
+ // both must have or both must not have extent
+ return false;
+ }
+
+ if (this.extentIndex != null && this.extentIndex != that.extentIndex)
+ {
+ // if both have but it is different
return false;
}
return propertyName.equals(that.propertyName);
}
-
+
/**
* Get the {@link #propertyMeta}.
*
@@ -13389,7 +13656,7 @@
{
return accessor;
}
-
+
/**
* Get field name.
*
@@ -13410,6 +13677,19 @@
{
return extentIndex;
}
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder("DatumAccess{");
+ sb.append(propertyName).append("/").append(propertyMeta.getLegacyName());
+ if (extent != null && extent > 0)
+ {
+ sb.append("[").append(extent).append("]");
+ }
+ sb.append("}");
+ return sb.toString();
+ }
}
/**
@@ -13650,6 +13930,171 @@
// should be unreachable
return null;
}
-
+ }
+
+ /**
+ * Obtain a string representation of the table content stored on SQL. This is a SQL-wise view of the
+ * primary table of the DMO, not an actual ABL data. Only to be used in debug purposes.
+ * Notes:
+ *
+ *
the EXTENT properties are not shown unless the table is denormalized (in which case the these
+ * properties are stored in the primary table);
+ *
the name of the columns in the returned string are the SQL column names, not the legacy names;
+ *
the hidden, reserved before/after-table specific attributes are visible for temp-tables;
+ *
the larger string/character values are cut to max 32 chars;
+ *
+ *
+ * @param limit
+ * The maximum number of rows to be retrieved. Use 0 or negative to get them all.
+ *
+ * @return A string with the table content in a tabular format.
+ *
+ * @throws PersistenceException
+ * @throws SQLException
+ * If any issue was encountered. Since this method is supposed to be called only in debug mode,
+ * it's up to programmer to decide what went wrong.
+ */
+ public String sqlTableContent(int limit)
+ throws PersistenceException, SQLException
+ {
+ String sql = "select * from " + dmoInfo.sqlTable;
+ if (limit > 0)
+ {
+ sql += " limit " + limit;
+ }
+
+ ResultSet res = persistence.getSession().getConnection().prepareStatement(sql).executeQuery();
+ ResultSetMetaData md = res.getMetaData();
+ int cc = md.getColumnCount();
+ int[] lens = new int[cc];
+
+ List rows = new ArrayList<>(10);
+ while (res.next())
+ {
+ String[] row = new String[cc];
+ for (int k = 1; k <= cc; k++)
+ {
+ row[k - 1] = res.getString(k);
+ int length = row[k - 1] == null ? 4 : row[k - 1].length();
+ if (lens[k - 1] < length)
+ {
+ lens[k - 1] = length;
+ }
+ }
+ rows.add(row);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("ABL: ").append(dmoInfo.legacyTable).append("/ SQL:").append(dmoInfo.sqlTable).append("\n");
+ for (int k = 1; k <= cc; k++)
+ {
+ String label = md.getColumnLabel(k);
+ if (lens[k - 1] < label.length())
+ {
+ lens[k - 1] = label.length();
+ }
+
+ if (lens[k - 1] > 32)
+ {
+ lens[k - 1] = 32;
+ }
+ else if (lens[k - 1] < 4)
+ {
+ lens[k - 1] = 4;
+ }
+
+ if (k != 1)
+ {
+ sb.append(" | ");
+ }
+ fit(sb, label, lens[k - 1], md.getColumnType(k));
+ }
+ sb.append("\n");
+
+ for (int k = 1; k <= cc; k++)
+ {
+ if (k != 1)
+ {
+ sb.append("-+-");
+ }
+ int len = lens[k - 1];
+ while (len-- > 0)
+ {
+ sb.append("-");
+ }
+ }
+ sb.append("\n");
+
+ for (String[] row : rows)
+ {
+ for (int k = 1; k <= cc; k++)
+ {
+ if (k != 1)
+ {
+ sb.append(" | ");
+ }
+ fit(sb, row[k - 1], lens[k - 1], md.getColumnType(k));
+ }
+
+ sb.append("\n");
+ }
+
+ res.close();
+ return sb.toString();
+ }
+
+ /**
+ * Helper method for {@link #sqlTableContent(int)}. Used for generating the tabular pretty format.
+ *
+ * @param sb {@code StringBuilder} to put the result into.
+ * The
+ * @param text
+ * The value to be formatted.
+ * @param size
+ * The available space.
+ * @param type
+ * The column type. Used for alignment.
+ */
+ private void fit(StringBuilder sb, String text, int size, int type)
+ {
+ boolean leftAlign = false;
+ switch (type)
+ {
+ case Types.NCHAR:
+ case Types.NVARCHAR:
+ case Types.CHAR:
+ case Types.VARCHAR:
+ case Types.LONGVARCHAR:
+ leftAlign = true;
+ break;
+ }
+
+ String ret;
+ if (text == null)
+ {
+ ret = "null";
+ }
+ else if (text.length() <= size)
+ {
+ ret = text;
+ }
+ else
+ {
+ ret = text.substring(0, size);
+ }
+
+ int k = size - ret.length();
+ if (leftAlign)
+ {
+ sb.append(ret);
+ }
+ while (k-- > 0)
+ {
+ sb.append(" ");
+ }
+ if (!leftAlign)
+ {
+ sb.append(ret);
+ }
}
}
=== removed file 'src/com/goldencode/p2j/persist/Reversible.java'
--- src/com/goldencode/p2j/persist/Reversible.java 2020-09-06 23:15:41 +0000
+++ src/com/goldencode/p2j/persist/Reversible.java 1970-01-01 00:00:00 +0000
@@ -1,254 +0,0 @@
-/*
-** Module : Reversible.java
-** Abstract : Abstract base class for reversible database actions
-**
-** Copyright (c) 2004-2020, Golden Code Development Corporation.
-**
-** -#- -I- --Date-- --JPRM-- ---------------------------Description------------------------------
-** 001 ECF 20070130 @32042 Created initial version. Moved implementation
-** from inner class of RecordBuffer to a top
-** level class.
-** 002 ECF 20071221 @36547 Modified constructor. Throw NPE if dmo
-** parameter is null.
-** 003 CA 20080604 @38538 Allow the DMO to be changed (when a DELETE or
-** CREATE action is passed to the parent block,
-** the DMO must be set to the chain's DMO).
-** Added new c'tor variant.
-** 004 CA 20080606 @38598 Added duplicate() method.
-** 005 ECF 20080628 @39071 Track whether DMO was newly created.
-** 006 ECF 20080925 @39944 Refactored to reduce memory use. Some
-** functionality moved to AbstractReversible and
-** concrete subclasses.
-** 007 ECF 20081020 @40210 Added methods necessary to fix rollback
-** problems at the full transaction level.
-** 008 CA 20091202 @44468 Removed the newlyCreated c'tor parameter - the
-** flag will be set on rollback, depending on the
-** existence of Create operations; currently is set
-** only for undoable tables.
-** 009 ECF 20140305 Replaced Apache commons logging with J2SE logging.
-** 010 ECF 20200906 New ORM implementation.
-*/
-
-/*
-** This program is free software: you can redistribute it and/or modify
-** it under the terms of the GNU Affero General Public License as
-** published by the Free Software Foundation, either version 3 of the
-** License, or (at your option) any later version.
-**
-** This program is distributed in the hope that it will be useful,
-** but WITHOUT ANY WARRANTY; without even the implied warranty of
-** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-** GNU Affero General Public License for more details.
-**
-** You may find a copy of the GNU Affero GPL version 3 at the following
-** location: https://www.gnu.org/licenses/agpl-3.0.en.html
-**
-** Additional terms under GNU Affero GPL version 3 section 7:
-**
-** Under Section 7 of the GNU Affero GPL version 3, the following additional
-** terms apply to the works covered under the License. These additional terms
-** are non-permissive additional terms allowed under Section 7 of the GNU
-** Affero GPL version 3 and may not be removed by you.
-**
-** 0. Attribution Requirement.
-**
-** You must preserve all legal notices or author attributions in the covered
-** work or Appropriate Legal Notices displayed by works containing the covered
-** work. You may not remove from the covered work any author or developer
-** credit already included within the covered work.
-**
-** 1. No License To Use Trademarks.
-**
-** This license does not grant any license or rights to use the trademarks
-** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
-** of Golden Code Development Corporation. You are not authorized to use the
-** name Golden Code, FWD, or the names of any author or contributor, for
-** publicity purposes without written authorization.
-**
-** 2. No Misrepresentation of Affiliation.
-**
-** You may not represent yourself as Golden Code Development Corporation or FWD.
-**
-** You may not represent yourself for publicity purposes as associated with
-** Golden Code Development Corporation, FWD, or any author or contributor to
-** the covered work, without written authorization.
-**
-** 3. No Misrepresentation of Source or Origin.
-**
-** You may not represent the covered work as solely your work. All modified
-** versions of the covered work must be marked in a reasonable way to make it
-** clear that the modified work is not originating from Golden Code Development
-** Corporation or FWD. All modified versions must contain the notices of
-** attribution required in this license.
-*/
-
-package com.goldencode.p2j.persist;
-
-import java.util.logging.*;
-import com.goldencode.p2j.util.LogHelper;
-
-/**
- * Abstract base class of various types of database actions that must be
- * reversible. Invocation of the {@link #rollback(boolean, boolean)} method
- * reverses a specific database action on that record. The action taken to
- * roll back the original action depends upon the subclass' implementation of
- * the rollback method.
- */
-abstract class Reversible
-{
- /** Logger */
- protected static final Logger LOG = LogHelper.getLogger(Reversible.class);
-
- /** Was DMO record newly created at the time Reversible was created? */
- private boolean newlyCreated;
-
- /**
- * Get a string representation of the internal state of this object,
- * primarily for debugging purposes.
- *
- * @return String representing this object's state.
- */
- public String toString()
- {
- return getAction() + " of " + getDMOName() + " [ID " + getId() + "]";
- }
-
- /**
- * Get a string which briefly describes the action which this object
- * will reverse upon rollback.
- *
- * @return Action string.
- */
- protected String getAction()
- {
- return null;
- }
-
- /**
- * Indicate whether this Reversible implementation stores its
- * DMO locally, as opposed to storing an identifier and having to retrieve
- * the DMO from the backing database, or not making a DMO available at all.
- *
- * @return true if the DMO is stored locally, else
- * false.
- */
- protected abstract boolean storesDMO();
-
- /**
- * Get the unqualified interface name of the DMO being managed by this
- * reversible.
- *
- * @return DMO interface name.
- */
- protected abstract String getDMOName();
-
- /**
- * Roll back the database change represented by this object.
- *
- * Subclasses must implement this method in a manner appropriate to
- * the database actions they represent.
- *
- * @param rollForward
- * If true, this method is being invoked in order to
- * re-roll forward a non-undoable change, after a database-level
- * rollback. This indicates the modification is made within a
- * new transaction invoked after the database-level rollback.
- * If false, this method is being invoked in order to
- * roll back an undoable change within the context of an
- * application-level, full transaction or sub-transaction
- * rollback.
- * @param transaction
- * true if this rollback occurs at a full transaction
- * boundary, else false.
- *
- * @throws PersistenceException
- * if an error occurs at the database or in the persistence
- * layer when reversing the action.
- */
- protected abstract void rollback(boolean rollForward, boolean transaction)
- throws PersistenceException;
-
- /**
- * Get the underlying DMO.
- *
- * @return DMO.
- */
- protected abstract Record getDMO();
-
- /**
- * Set the underlying DMO for this reversible action.
- *
- * @param dmo
- * The underlying DMO; may not be null.
- *
- * @throws NullPointerException
- * if dmo is null.
- */
- protected abstract void setDMO(Record dmo);
-
- /**
- * Get the ID of the underlying DMO.
- *
- * @return DMO ID.
- */
- protected abstract Long getId();
-
- /**
- * Prepare for this reversible to be rolled back. Generally, this will
- * involve readying the DMO which will be acted upon by the rollback
- * actions.
- *
- * @param dmo
- * If non-null, the caller is providing a DMO which
- * it thinks is the appropriate object to act upon during rollback
- * processing.
- */
- protected void prepareRollback(Record dmo)
- {
- }
-
- /**
- * Indicate whether the DMO which is the target of a rollback was newly
- * created by the current context.
- *
- * @return true to indicate target DMO was newly created by
- * the current context; else false.
- */
- protected boolean isNewlyCreated()
- {
- return newlyCreated;
- }
-
- /**
- * Indicate whether the DMO which is the target of a rollback was newly
- * created by the current context.
- *
- * @param isNew
- * true to indicate dmo is newly created
- * by the current context; else false.
- */
- protected void setNewlyCreated(boolean isNew)
- {
- this.newlyCreated = isNew;
- }
-
- /**
- * Duplicate this reversible action. Implementations which require
- * this behavior must override this method.
- *
- * @param key
- * The key to set for the new reversible action.
- *
- * @return This implementation does not return normally; override to
- * return a duplicate of this reversible.
- *
- * @throws UnsupportedOperationException
- * If this operation is not supported.
- */
- Reversible duplicate(Long key)
- {
- throw new UnsupportedOperationException(
- "Duplicate is not supported for reversible " +
- this.toString());
- }
-}
=== added file 'src/com/goldencode/p2j/persist/SerializeHiddenable.java'
--- src/com/goldencode/p2j/persist/SerializeHiddenable.java 1970-01-01 00:00:00 +0000
+++ src/com/goldencode/p2j/persist/SerializeHiddenable.java 2020-11-18 21:54:53 +0000
@@ -0,0 +1,102 @@
+/*
+** Module : SerializeHiddenable.java
+** Abstract : Declares the public accessors of SERIALIZE-HIDDEN attribute of DataSet/BufferField.
+**
+** Copyright (c) 2020, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** 001 OM 20190403 Created initial version.
+*/
+
+/*
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU Affero General Public License as
+** published by the Free Software Foundation, either version 3 of the
+** License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU Affero General Public License for more details.
+**
+** You may find a copy of the GNU Affero GPL version 3 at the following
+** location: https://www.gnu.org/licenses/agpl-3.0.en.html
+**
+** Additional terms under GNU Affero GPL version 3 section 7:
+**
+** Under Section 7 of the GNU Affero GPL version 3, the following additional
+** terms apply to the works covered under the License. These additional terms
+** are non-permissive additional terms allowed under Section 7 of the GNU
+** Affero GPL version 3 and may not be removed by you.
+**
+** 0. Attribution Requirement.
+**
+** You must preserve all legal notices or author attributions in the covered
+** work or Appropriate Legal Notices displayed by works containing the covered
+** work. You may not remove from the covered work any author or developer
+** credit already included within the covered work.
+**
+** 1. No License To Use Trademarks.
+**
+** This license does not grant any license or rights to use the trademarks
+** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
+** of Golden Code Development Corporation. You are not authorized to use the
+** name Golden Code, FWD, or the names of any author or contributor, for
+** publicity purposes without written authorization.
+**
+** 2. No Misrepresentation of Affiliation.
+**
+** You may not represent yourself as Golden Code Development Corporation or FWD.
+**
+** You may not represent yourself for publicity purposes as associated with
+** Golden Code Development Corporation, FWD, or any author or contributor to
+** the covered work, without written authorization.
+**
+** 3. No Misrepresentation of Source or Origin.
+**
+** You may not represent the covered work as solely your work. All modified
+** versions of the covered work must be marked in a reasonable way to make it
+** clear that the modified work is not originating from Golden Code Development
+** Corporation or FWD. All modified versions must contain the notices of
+** attribution required in this license.
+*/
+
+package com.goldencode.p2j.persist;
+
+import com.goldencode.p2j.util.*;
+
+/**
+ * Declares the public accessors of SERIALIZE-HIDDEN attribute of {@code DataSet}/ {@code BufferField}.
+ */
+public interface SerializeHiddenable
+{
+ /**
+ * Checks whether fields are written when the temp-table is serialized, usually as JSON or XML.
+ * This represents the getter for {@code SERIALIZE-HIDDEN} ABL attribute.
+ *
+ * @return {@code true} when the fields are skipped from serialization.
+ */
+ @LegacyAttribute(name = "SERIALIZE-HIDDEN")
+ public logical getSerializeHidden();
+
+ /**
+ * Allow to configure whether fields are written when the temp-table is serialized, usually as JSON
+ * or XML. This represents the setter for {@code SERIALIZE-HIDDEN} ABL attribute.
+ *
+ * @param val
+ * {@code true} when the fields are to be skipped from serialization.
+ */
+ @LegacyAttribute(name = "SERIALIZE-HIDDEN", setter = true)
+ public void setSerializeHidden(logical val);
+
+ /**
+ * Allow to configure whether the filed is written when the temp-table is serialized, usually as JSON
+ * or XML. This represents the setter for {@code SERIALIZE-HIDDEN} ABL attribute.
+ *
+ * @param val
+ * {@code true} when the fields are to be skipped from serialization.
+ */
+ @LegacyAttribute(name = "SERIALIZE-HIDDEN", setter = true)
+ public void setSerializeHidden(boolean val);
+}
+
=== modified file 'src/com/goldencode/p2j/persist/SortCriterion.java'
--- src/com/goldencode/p2j/persist/SortCriterion.java 2020-10-01 22:14:40 +0000
+++ src/com/goldencode/p2j/persist/SortCriterion.java 2020-10-09 21:13:00 +0000
@@ -90,6 +90,8 @@
** 035 ECF 20200906 New ORM implementation.
** 036 OM 20201001 Dropped redundant ORDER BY elements in multi-table queries.
** OM 20201001 Improved DMO manipulation performance by caching slow Property annotation access.
+** OM 20201007 Optimized SortCriterion by using DmoMeta data instead of map lookups. Simplified
+** API by dropping unneeded parameters. Optimized sort phrase parsing.
*/
/*
@@ -211,6 +213,7 @@
/** Flag indicating if the 'withNulls' is activated in {@link #toWhereExpression}. */
private static final boolean WHERE_WITH_NULLS =
"true".equalsIgnoreCase(System.getProperty("WHERE_WITH_NULLS"));
+
/** Shared cache of immutable {@code SortCriterion} lists */
private static final ExpiryCache> cache = new LRUCache<>(65536);
@@ -242,7 +245,7 @@
private final String propertyName;
/** Sort property name, including subscript if any, usually qualified */
- private String originalName;
+ private final String originalName;
/** Sort property name, usually qualified */
private String name;
@@ -253,9 +256,6 @@
/** SQL dialect which used this sort criterion; used for rtrim calls on character properties */
private Dialect dialect = null;
- /** The target database. */
- private final Database database;
-
/**
* Constructor which parses original sort component text, deriving the property name, sort
* direction, getter method for the property, case sensitivity of the property (character type
@@ -271,22 +271,15 @@
* Record buffer.
* @param text
* Text representation of sort component, as provided by query.
- * @param dmoIface
- * DMO interface.
- * @param dmoClass
- * DMO implementation class.
*
* @throws PersistenceException
* if there is any error with the text representation of the sort
* component provided by the caller.
*/
- SortCriterion(RecordBuffer buffer,
- String text,
- Class extends DataModelObject> dmoIface,
- Class extends Record> dmoClass)
+ SortCriterion(RecordBuffer buffer, String text)
throws PersistenceException
{
- this(buffer.getDialect(), buffer.getDatabase(), text, dmoIface, dmoClass, false);
+ this(buffer.getDialect(), text, buffer.dmoInfo, false);
}
/**
@@ -304,27 +297,17 @@
*
* @param dialect
* Database dialect.
- * @param database
- * The target database.
* @param text
* Text representation of sort component, as provided by query.
- * @param dmoIface
- * DMO interface.
- * @param dmoClass
- * DMO implementation class.
*
* @throws PersistenceException
* if there is any error with the text representation of the sort
* component provided by the caller.
*/
- SortCriterion(Dialect dialect,
- Database database,
- String text,
- Class extends DataModelObject> dmoIface,
- Class extends Record> dmoClass)
+ SortCriterion(Dialect dialect, String text, DmoMeta dmoInfo)
throws PersistenceException
{
- this(dialect, database, text, dmoIface, dmoClass, true);
+ this(dialect, text, dmoInfo, true);
}
/**
@@ -337,14 +320,10 @@
*
* @param dialect
* Database dialect.
- * @param database
- * The target database.
* @param text
* Text representation of sort component, as provided by query.
- * @param dmoIface
- * DMO interface.
- * @param dmoClass
- * DMO implementation class.
+ * @param dmoInfo
+ * Metadata for the table.
* @param internStrings
* {@code true} to intern string fields, else {@code false}.
*
@@ -352,24 +331,20 @@
* if there is any error with the text representation of the sort
* component provided by the caller.
*/
- SortCriterion(Dialect dialect,
- Database database,
- String text,
- Class extends DataModelObject> dmoIface,
- Class extends Record> dmoClass,
- boolean internStrings)
+ SortCriterion(Dialect dialect, String text, DmoMeta dmoInfo, boolean internStrings)
throws PersistenceException
{
this.dialect = dialect;
- this.database = database;
- this.dmoClass = dmoClass;
- String[] tmp = text.split(" ");
- String rawName = tmp[0].trim();
+ this.dmoClass = dmoInfo.getImplementationClass();
+
+ // parse the test and extract alias, property and optional extent
+ int spacePos = text.indexOf(' ');
+ String rawName = ((spacePos != -1) ? text.substring(0, spacePos) : text).trim();
name = rawName;
- originalName = (internStrings ? rawName.intern() : rawName);
- int startPos = rawName.indexOf("[");
+ originalName = internStrings ? rawName.intern() : rawName;
+ int bracketPos = rawName.indexOf("[");
int endPos = -1;
- if (startPos >= 0)
+ if (bracketPos >= 0)
{
endPos = rawName.indexOf("]");
if (endPos < 0)
@@ -377,39 +352,30 @@
throw new PersistenceException("Malformed subscript text: " + text);
}
- // Extract base name.
- name = rawName.substring(0, startPos);
+ // extract base name
+ name = rawName.substring(0, bracketPos);
}
- int dot = name.lastIndexOf(".");
- if (dot < 0)
+ int dotPos = name.lastIndexOf(".");
+ if (dotPos < 0)
{
throw new PersistenceException("Sort property may not be unqualified: " + rawName);
}
- String propName = name.substring(dot + 1);
- String alias = name.substring(0, dot);
-
- if (internStrings)
- {
- this.propertyName = propName.intern();
- this.alias = alias.intern();
- }
- else
- {
- this.propertyName = propName;
- this.alias = alias;
- }
-
- if (startPos >= 0)
- {
- DmoMeta dmoMeta = DmoMetadataManager.getDmoInfo(dmoClass);
- Property extProperty = dmoMeta.getFieldInfo(propertyName);
- int extent = extProperty.index > 0 ? 0 : extProperty.extent;
+ String propName = name.substring(dotPos + 1);
+ String alias = name.substring(0, dotPos);
+ DmoMeta dmoMeta = DmoMetadataManager.getDmoInfo(dmoClass);
+ Property propMeta = dmoMeta.getFieldInfo(propName);
+ this.propertyName = propMeta.name; // already interned, anyway
+ this.alias = internStrings ? alias.intern() : alias;
+
+ if (bracketPos >= 0)
+ {
+ int extent = propMeta.index > 0 ? 0 : propMeta.extent;
if (extent > 0)
{
// extract subscript
- String sub = rawName.substring(startPos + 1, endPos);
+ String sub = rawName.substring(bracketPos + 1, endPos);
try
{
subscript = Integer.parseInt(sub);
@@ -428,11 +394,11 @@
name = name.intern();
}
- // Determine sort direction.
- ascending = (tmp.length == 1 || !"desc".equalsIgnoreCase(tmp[1].trim()));
+ // determine sort direction
+ ascending = (spacePos == -1 || !"desc".equalsIgnoreCase(text.substring(spacePos + 1).trim()));
- // Get backing getter method.
- if (TemporaryBuffer.MULTIPLEX_FIELD_NAME.equals(propertyName))
+ // get backing getter method
+ if (propMeta.id == ReservedProperty.ID_MULTIPLEX)
{
method = null;
isCharacter = false;
@@ -442,21 +408,21 @@
else
{
String ccPrefix = null;
- Class> iface = DatabaseManager.PRIMARY_KEY.equals(propertyName)
- ? Persistable.class
- : dmoIface;
- method = PropertyHelper.allGettersByProperty(iface).get(propertyName);
+ method = propMeta.id == ReservedProperty.ID_PRIMARY_KEY
+ ? PropertyHelper.allGettersByProperty(Persistable.class).get(propertyName)
+ : propMeta.annMethod;
- // Determine case sensitivity and computed column prefix, if needed.
+ // determine case sensitivity and computed column prefix, if needed
boolean caseSens = false;
- isCharacter = character.class.equals(method.getReturnType());
+ isCharacter = propMeta._isCharacter;
if (isCharacter)
{
Boolean ccIgnoreCase = null;
if (dialect.needsComputedColumns())
{
- ccIgnoreCase = DatabaseManager.getIgnoreCase(dmoClass, propertyName);
+ ccIgnoreCase = dmoMeta.isIndexedIgnoreCase(propertyName);
}
+
if (ccIgnoreCase != null)
{
caseSens = !ccIgnoreCase;
@@ -464,8 +430,9 @@
}
else
{
- caseSens = DmoMetadataManager.isCaseSensitive(dmoIface, propertyName);
+ caseSens = propMeta.caseSensitive;
}
+
if (ccPrefix != null && internStrings)
{
ccPrefix = ccPrefix.intern();
@@ -477,44 +444,34 @@
}
/**
- * Parse a sort phrase (not including the "order by" preamble)
- * into a list of SortCriterion objects. All components of
- * the phrase are assumed to be properties of the same DMO.
+ * Parse a sort phrase (not including the {@code "order by"} preamble) into a list of {@code SortCriterion}
+ * objects. All components of the phrase are assumed to be properties of the same DMO.
*
* @param dialect
* Database dialect.
- * @param database
- * The target database.
* @param sort
* Sort phrase.
-// * @param schema
-// * Database schema.
* @param dmoAlias
* Alias qualifier which represents DMO in sort phrase.
- * @param dmoClass
- * DMO implementation class.
- * @param dmoIface
- * DMO interface.
+ * @param dmoInfo
+ * Metadata for the table.
* @param makeUnique
- * Indicates whether the <dmoAlias>.recid
- * criterion should be appended to the returned criteria list.
+ * Indicates whether the {@code .recid} criterion should be appended to the returned
+ * criteria list.
*
- * @return List of SortCriterion objects.
+ * @return List of {@code SortCriterion} objects.
*
* @throws PersistenceException
* if there is any error parsing the given string.
*/
public static List parse(Dialect dialect,
- Database database,
String sort,
- /*String schema,*/
String dmoAlias,
- Class extends DataModelObject> dmoIface,
- Class extends Record> dmoClass,
+ DmoMeta dmoInfo,
boolean makeUnique)
throws PersistenceException
{
- CacheKey key = new CacheKey(dialect, sort, /*schema,*/ dmoAlias, dmoClass, makeUnique);
+ CacheKey key = new CacheKey(dialect, sort, dmoAlias, dmoInfo.getImplementationClass(), makeUnique);
List list = null;
synchronized (cacheLock)
@@ -538,7 +495,7 @@
String[] sortComponents = sort.split(",");
for (String sortComp : sortComponents)
{
- next = new SortCriterion(dialect, database, sortComp.trim(), /*schema,*/ dmoIface, dmoClass);
+ next = new SortCriterion(dialect, sortComp.trim(), dmoInfo);
sortCriteria.add(next);
sortNames.add(next.getUnqualifiedName());
}
@@ -554,16 +511,15 @@
// criteria, if not redundant with the previous criteria.
if (makeUnique && !sortNames.contains(DatabaseManager.PRIMARY_KEY))
{
- String sortComp = dmoAlias + "." + DatabaseManager.PRIMARY_KEY + " " +
- (pKeyAscend ? "asc" : "desc");
- sortCriteria.add(new SortCriterion(dialect, database, sortComp, /*schema,*/ dmoIface, dmoClass));
+ String sortComp = dmoAlias + "." + DatabaseManager.PRIMARY_KEY + " " + (pKeyAscend ? "asc" : "desc");
+ sortCriteria.add(new SortCriterion(dialect, sortComp, dmoInfo));
}
// eliminate superfluous sort components if sort criteria define a superset of a unique
// constraint.
int size = sortCriteria.size();
int lastUniqueComp = size;
- Iterator> uniques = DmoMetadataManager.uniqueConstraints(/*schema,*/ dmoIface);
+ Iterator> uniques = dmoInfo.getUniqueConstraints().iterator();
while (uniques.hasNext())
{
Set compSet = uniques.next();
@@ -649,14 +605,7 @@
static List parse(String sort, RecordBuffer buffer, boolean makeUnique)
throws PersistenceException
{
- Dialect dialect = buffer.getDialect();
- Database database = buffer.getDatabase();
-// String schema = buffer.getSchema();
- String dmoAlias = buffer.getDMOAlias();
- Class extends Record> dmoClass = buffer.getDMOImplementationClass();
- Class extends DataModelObject> dmoIface = buffer.getDMOInterface();
-
- return parse(dialect, database, sort, /*schema,*/ dmoAlias, dmoIface, dmoClass, makeUnique);
+ return parse(buffer.getDialect(), sort, buffer.getDMOAlias(), buffer.dmoInfo, makeUnique);
}
/**
=== modified file 'src/com/goldencode/p2j/persist/SourceData.java'
--- src/com/goldencode/p2j/persist/SourceData.java 2020-05-03 20:30:03 +0000
+++ src/com/goldencode/p2j/persist/SourceData.java 2021-01-29 00:53:41 +0000
@@ -2,14 +2,16 @@
** Module : SourceData.java
** Abstract : Describes an external source from which data is read into a buffer.
**
-** Copyright (c) 2017-2020, Golden Code Development Corporation.
+** Copyright (c) 2017-2021, Golden Code Development Corporation.
**
-** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 ECF 20170820 Created initial version.
** 002 ECF 20171008 Implemented file as an output resource.
** 003 ECF 20190120 Added implementation for c'tor which accepts longchar.
** 004 OM 20190327 Renamed to SourceData to avoid conflicts with DataSet source.
** 005 CA 20200503 Added c'tor to read data from a JsonObject instance (stubbed).
+** 006 OM 20201120 Implementing Closeable interface. Lazily open the file stream.
+** OM 20210120 Added full type support, including validation and error handling.
*/
/*
@@ -25,39 +27,39 @@
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
-**
+**
** Additional terms under GNU Affero GPL version 3 section 7:
-**
+**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
-**
+**
** 0. Attribution Requirement.
-**
+**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
-**
+**
** 1. No License To Use Trademarks.
-**
+**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
-**
+**
** 2. No Misrepresentation of Affiliation.
-**
+**
** You may not represent yourself as Golden Code Development Corporation or FWD.
-**
+**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
-**
+**
** 3. No Misrepresentation of Source or Origin.
-**
+**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
@@ -67,121 +69,339 @@
package com.goldencode.p2j.persist;
+import com.goldencode.p2j.persist.serial.*;
import com.goldencode.p2j.util.*;
-
import java.io.*;
+import java.util.*;
/**
- * Object which describes an external source from which data is read into a temp-table buffer
- * or data set member buffer.
+ * Object which describes an external source from which data is read into a temp-table buffer or dataset
+ * member buffer.
*/
public class SourceData
+implements Serializator
{
/** Normalized input stream */
private InputStream stream = null;
- /**
- * Constructor.
- *
- * @param source
- * Object from which to read data. This should be a
- * {@link com.goldencode.p2j.oo.json.objectmodel.JsonConstruct}.
- * WARNING: do not add generics to the parameter, as this creates problems in compile.
- */
- public SourceData(object source)
- {
- UnimplementedFeature.missing("SourceData(jsonObject)");
- }
-
- /**
- * Constructor.
- *
- * @param source
- * Object from which to read data.
- */
- public SourceData(character source)
- {
- initializeRemote(source.toStringMessage());
- }
-
- /**
- * Constructor.
- *
- * @param source
- * Object from which to read data.
- */
- public SourceData(String source)
- {
- initializeRemote(source);
- }
-
- /**
- * Constructor.
- *
- * @param source
- * Pointer to area of memory from which to read data.
- */
- public SourceData(memptr source)
- {
- UnimplementedFeature.missing("SourceData c'tor");
- }
-
- /**
- * Constructor.
- *
- * @param source
- * Handle of object from which to read data.
- */
- public SourceData(handle source)
- {
- UnimplementedFeature.missing("SourceData c'tor");
- }
-
- /**
- * Constructor.
- *
- * @param source
- * Object from which to read data.
- */
- public SourceData(longchar source)
- {
- // TODO: making a copy of the backing byte array could pose a memory problem for large
- // longchar values
- stream = new ByteArrayInputStream(source.asByteArray(0, source.lengthOf()));
- }
-
- /**
- * Get the input stream representing the source resource from which data will be read.
- * This may be a remote or a local resource.
- *
+ /** The file name if such source was created. */
+ private String fileName = null;
+
+ /**
+ * Constant used to identify the calls from READ-XML method. Also used to store the supported stream types
+ * for this method.
+ */
+ public static final String SD_READ_XML = "READ-XML";
+
+ /**
+ * Constant used to identify the calls from READ-XMLSCHEMA method. Also used to store the supported stream
+ * types for this method.
+ */
+ public static final String SD_READ_XMLSCHEMA = "READ-XMLSCHEMA";
+
+ /**
+ * Constant used to identify the calls from READ-JSON method. Also used to store the supported stream types
+ * for this method.
+ */
+ public static final String SD_READ_JSON = "READ-JSON";
+
+ /* The initialization of static member data. */
+ private static final Map supportedStreams = new HashMap<>();
+ static
+ {
+ supportedStreams.put(SD_READ_XML, SER_FILE | SER_MEMPTR | SER_HANDLE | SER_LONGCHAR);
+ supportedStreams.put(SD_READ_XMLSCHEMA, SER_FILE | SER_MEMPTR | SER_HANDLE | SER_LONGCHAR);
+ supportedStreams.put(SD_READ_JSON,
+ SER_FILE | SER_MEMPTR | SER_HANDLE | SER_LONGCHAR | SER_JSON_ARRAY | SER_JSON_OBJECT);
+ }
+
+ /** Local copy of the stream type configured in constructor. */
+ private final String origType;
+
+ /** Local reference to stream (or stream name), as configured in constructor. */
+ private final BaseDataType origSrc;
+
+ /**
+ * The constructor saves the configured parameters but does not process them in any way. The validation
+ * will be executed later, when the calling method is in progress.
+ *
+ * @param type
+ * The source stream type.
+ * @param source
+ * The reference to resource or the file name.
+ */
+ public SourceData(character type, BaseDataType source)
+ {
+ this((type == null) ? null : type.toJavaType(), source);
+ }
+
+ /**
+ * The constructor saves the configured parameters but does not process them in any way. The validation
+ * will be executed later, when the calling method is in progress.
+ *
+ * @param type
+ * The source stream type.
+ * @param source
+ * The reference to resource or the file name.
+ */
+ public SourceData(String type, BaseDataType source)
+ {
+ origType = type;
+ origSrc = source;
+ }
+
+ /**
+ * The constructor saves the configured parameters but does not process them in any way. The validation
+ * will be executed later, when the calling method is in progress.
+ *
+ * @param type
+ * The source stream type.
+ * @param source
+ * The file name.
+ */
+ public SourceData(String type, String source)
+ {
+ origType = type;
+ origSrc = new character(source);
+ }
+
+ /**
+ * Configures the resource stream types for the current usage. The caller passes in its name and the
+ * method does the selection. It also does the verification whether the already configured parameters (in
+ * the constructor) are valid and eventual raise the error condition.
+ *
+ * @param method
+ * The identifier for the method using the source stream. It is used to get the set of acceptable
+ * stream types to check against them.
+ * @param widgetType
+ * The parent widget. Only used for composing the error messages.
+ *
+ * @return {@code true} if the parameters configured in constructor seems correct. This method does not
+ * check the content, only the variable types. If there is not a match with the possible streams
+ * supported by the {@code method}, {@code false} is returned, after eventually raising a specific
+ * error condition.
+ */
+ public boolean configureSupportedSources(String method, String widgetType)
+ {
+ boolean isJson = method.contains("JSON");
+ if (origType == null)
+ {
+ ErrorManager.recordOrShowError(5442, method, "character");
+ // Invalid datatype for argument to method ''. Expecting ''
+ return false;
+ }
+
+ if (origSrc == null || origSrc.isUnknown())
+ {
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ return false;
+ }
+
+ // [method] must always be one of the supported methods
+ int supportedSources = supportedStreams.get(method);
+
+ switch (origType.toUpperCase())
+ {
+ case TYPE_FILE:
+ if ((supportedSources & SER_FILE) != 0)
+ {
+ if ((origSrc instanceof longchar))
+ {
+ // no error, but false
+ return false;
+ }
+
+ if (!(origSrc instanceof character))
+ {
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ return false;
+ }
+ return true;
+ }
+ break;
+
+ case TYPE_STREAM:
+ if ((supportedSources & SER_STREAM) != 0)
+ {
+ return true;
+ }
+ break;
+
+ case TYPE_STREAM_HANDLE:
+ if ((supportedSources & SER_STREAM_HANDLE) != 0)
+ {
+ return true;
+ }
+ break;
+
+ case TYPE_MEMPTR:
+ if ((supportedSources & SER_MEMPTR) != 0)
+ {
+ if (!(origSrc instanceof memptr))
+ {
+ if (isJson)
+ {
+ ErrorManager.recordOrShowError(15362);
+ // READ-JSON source is not a valid LONGCHAR or MEMPTR. (15362)
+ ErrorManager.recordOrShowError(17956);
+ // Unable to create reader for READ-JSON (17956)
+ }
+ else
+ {
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ }
+ return false;
+ }
+ return true;
+ }
+ break;
+
+ case TYPE_HANDLE:
+ if (isJson)
+ {
+ ErrorManager.recordOrShowError(15356);
+ // Handle type not valid as JSON input source. (15356)
+ return false;
+ }
+ else
+ {
+ ErrorManager.recordOrShowError(10515);
+ // Handle type not valid as XML input source. (10515)
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ return false;
+ }
+
+ case TYPE_LONGCHAR:
+ if ((supportedSources & SER_LONGCHAR) != 0)
+ {
+ if (!(origSrc instanceof longchar))
+ {
+ if (isJson)
+ {
+ ErrorManager.recordOrShowError(15362);
+ // READ-JSON source is not a valid LONGCHAR or MEMPTR. (15362)
+ ErrorManager.recordOrShowError(17956);
+ // Unable to create reader for READ-JSON (17956)
+ }
+ else
+ {
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ return false;
+ }
+ return false;
+ }
+ return true;
+ }
+ break;
+
+ case TYPE_JSON_OBJECT:
+ if ((supportedSources & SER_JSON_OBJECT) != 0)
+ {
+ return true;
+ }
+ break;
+
+ case TYPE_JSON_ARRAY:
+ if ((supportedSources & SER_JSON_ARRAY) != 0)
+ {
+ return true;
+ }
+ break;
+ }
+
+ ErrorManager.recordOrShowError(isJson ? 15357 : 13184, origType);
+ // err 13184: Invalid source-type for READ-XML: .
+ // err 15357: Invalid source-type for READ-JSON: .
+ return false;
+ }
+
+ /**
+ * Get the input stream representing the source resource from which data will be read. This may be backed
+ * by a remote or a local resource.
+ *
* @return Input stream.
+ *
+ * @throws IOException
+ * if the method encounters issues with the resource.
*/
- public InputStream getInputStream()
+ @Override
+ public InputStream getStream()
+ throws IOException
{
- return stream;
+ // if already built, return it again
+ if (stream != null)
+ {
+ return stream;
+ }
+
+ switch (origType.toUpperCase())
+ {
+ case TYPE_FILE:
+ if (origSrc instanceof character)
+ {
+ fileName = ((character) origSrc).toJavaType();
+ try
+ {
+ stream = new InputStreamWrapper(StreamFactory.openFileStream(fileName, false, false));
+ }
+ catch (ErrorConditionException ece)
+ {
+ throw new FileNotFoundException(fileName);
+ }
+ return stream;
+ }
+ return null;
+
+ case TYPE_LONGCHAR:
+ if (origSrc instanceof longchar)
+ {
+ longchar source = (longchar) origSrc;
+ stream = new ByteArrayInputStream(source.asByteArray(0, source.lengthOf()));
+ return stream;
+ }
+ return null;
+
+ case TYPE_MEMPTR:
+ if (origSrc instanceof memptr)
+ {
+ memptr source = (memptr) origSrc;
+ stream = new ByteArrayInputStream(source.asByteArray(0, source.lengthOf()));
+ return stream;
+ }
+ return null;
+ }
+
+ return null;
}
/**
* Close the input stream underlying this data source.
- *
+ *
* @throws IOException
* if there is an error closing the stream.
*/
- void close()
+ public void close()
throws IOException
{
- stream.close();
+ if (stream != null)
+ {
+ stream.close();
+ }
}
/**
- * Initialize the remote input stream which backs this data source.
- *
- * @param filename
- * Client-side file name.
+ * Obtain the file name of the source, if one was used to create the source.
+ *
+ * @return the file name of the source, if one was used to create the source. Otherwise {@code null}.
*/
- private void initializeRemote(String filename)
+ public String getFileName()
{
- stream = new InputStreamWrapper(StreamFactory.openFileStream(filename, false, false));
+ return fileName;
}
}
=== modified file 'src/com/goldencode/p2j/persist/StaticTempTable.java'
--- src/com/goldencode/p2j/persist/StaticTempTable.java 2020-10-02 22:45:29 +0000
+++ src/com/goldencode/p2j/persist/StaticTempTable.java 2021-01-28 20:00:25 +0000
@@ -2,7 +2,7 @@
** Module : StaticTempTable.java
** Abstract : Static 4GL temp-table object.
**
-** Copyright (c) 2013-2020, Golden Code Development Corporation.
+** Copyright (c) 2013-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 SVL 20131115 Created initial version. Most of TODOs is just about throwing a proper error.
@@ -26,8 +26,12 @@
** 016 SVL 20190801 Added get/setCodePageSupplier.
** 017 ECF 20200906 New ORM implementation.
** 018 AIL 20200914 Implemented _prepared abstract method.
-** 019 AIL 20200915 Moved prepared method to superclass.
+** AIL 20200915 Moved prepared method to superclass.
** OM 20201002 Use DmoMeta cached information instead of map lookups.
+** OM 20201120 Extracted SERIALIZE-NAME to dedicated interface. Implemented CLEAR state awareness.
+** OM 20210106 Fixed 2nd parameter of TEMP-TABLE-PREPARE.
+** OM 20210127 Replaced TableMapper.getLegacyName() triple map lookup with direct access to local
+** dmoMeta.legacyTable.
*/
/*
@@ -97,7 +101,6 @@
*/
public class StaticTempTable
extends AbstractTempTable
-implements NamedSerializable
{
/**
* Code pages of CLOB fields keyed by hibernate property names.
@@ -108,7 +111,7 @@
* Code page suppliers of CLOB fields keyed by hibernate property names.
*/
private Map codePagesSuppliers;
-
+
/**
* Default constructor.
*
@@ -121,11 +124,11 @@
defaultBufferHandle = new handle();
defaultBufferHandle.assign(defaultBuffer);
+ TemporaryBuffer buffer = (TemporaryBuffer) ((BufferImpl) defaultBuffer).buffer();
TableMapper.mapTemporaryTable(this);
- String name = TableMapper.getLegacyName(this);
- this.name.assign(name);
- super.undoable = ((BufferImpl)defaultBuffer).buffer().isUndoable();
+ this.name.assign(buffer.dmoInfo.legacyTable);
+ super.undoable = buffer.isUndoable();
}
/**
@@ -763,8 +766,7 @@
@Override
public logical tempTablePrepare(character name)
{
- displayPreparedIgnoring();
- return new logical(false);
+ return tempTablePrepare((String) null);
}
/**
@@ -773,8 +775,7 @@
* This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
*
* @param name
- * Temp-table name to be used in subsequent query statements which refer to this
- * temp-table.
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
* @param before
* Create the {@code BEFORE-TABLE} also if {@code true}.
*
@@ -783,18 +784,52 @@
@Override
public logical tempTablePrepare(String name, boolean before)
{
- displayPreparedIgnoring();
- return new logical(false);
- }
-
- /**
- * Signals that all the field and index definitions for a temp-table have been provided. After
- * the call to this method no fields and indexes can be added to this temporary table.
- * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
- *
- * @param name
- * Temp-table name to be used in subsequent query statements which refer to this
- * temp-table.
+ return tempTablePrepare((String) null);
+ }
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After
+ * the call to this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
+ * @param name
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
+ * @param before
+ * Create the {@code BEFORE-TABLE} also if {@code true}.
+ *
+ * @return {@code true} on success.
+ */
+ @Override
+ public logical tempTablePrepare(String name, logical before)
+ {
+ return tempTablePrepare((String) null);
+ }
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After
+ * the call to this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
+ * @param name
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
+ * @param before
+ * Create the {@code BEFORE-TABLE} also if {@code true}.
+ *
+ * @return {@code true} on success.
+ */
+ @Override
+ public logical tempTablePrepare(character name, boolean before)
+ {
+ return tempTablePrepare((String) null);
+ }
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After
+ * the call to this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
+ * @param name
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
* @param before
* Create the {@code BEFORE-TABLE} also if {@code true}.
*
@@ -803,8 +838,7 @@
@Override
public logical tempTablePrepare(character name, logical before)
{
- displayPreparedIgnoring();
- return new logical(false);
+ return tempTablePrepare((String) null);
}
/**
@@ -814,12 +848,28 @@
*
* @return true if operation is successful
*/
- protected boolean _prepared()
+ public boolean _prepared()
{
return true;
}
/**
+ * Check whether this TEMP-TABLE is in CLEAR state: I.e. the temp-table is first created or immediately
+ * after the CLEAR() method is applied. The other two state of a temp-table are: UNPREPARED and PREPARED.
+ * The UNPREPARED state between the first definitional method has been applied and before the
+ * TEMP-TABLE-PREPARE() method is applied.
+ *
+ * This method is FWD-internal. The programmer can check whether the temp-table is in an UNPREPARED or
+ * PREPARED state by checking the PREPARED attribute, but the CLEAR state is transparent.
+ *
+ * @return Always {@code false} since the static temp-tables cannot be CLEAR-ed.
+ */
+ public boolean _clear()
+ {
+ return false;
+ }
+
+ /**
* Obtain the current UNDO attribute of 4GL temp-table.
*
* @return true if the the temp-table can undo.
@@ -1258,44 +1308,6 @@
{
return false;
}
-
- /**
- * Implementation of the read access of the {@code SERIALIZE-NAME} attribute.
- *
- * @return the current value of the {@code SERIALIZE-NAME} attribute.
- */
- @Override
- public character getSerializeName()
- {
- String sName = TableMapper.getSerializeOptions(this).getSerializeName();
- String serializeName = sName == null || sName.trim().isEmpty() ? name.getValue() : sName;
-
- return new character(serializeName);
- }
-
- /**
- * Implementation of the write access of the {@code SERIALIZE-NAME} attribute.
- *
- * @param sName
- * the new value for the {@code SERIALIZE-NAME} attribute.
- */
- @Override
- public void setSerializeName(Text sName)
- {
- setSerializeName(sName == null || sName.isUnknown() ? null : sName.getValue());
- }
-
- /**
- * Implementation of the write access of the {@code SERIALIZE-NAME} attribute.
- *
- * @param sName
- * the new value for the {@code SERIALIZE-NAME} attribute.
- */
- @Override
- public void setSerializeName(String sName)
- {
- TableMapper.getSerializeOptions(this).setSerializeName(sName == null ? "" : sName);
- }
/**
* Set code page supplier for the specified CLOB field.
=== modified file 'src/com/goldencode/p2j/persist/TableMapper.java'
--- src/com/goldencode/p2j/persist/TableMapper.java 2020-10-02 22:45:29 +0000
+++ src/com/goldencode/p2j/persist/TableMapper.java 2021-01-29 00:53:41 +0000
@@ -2,7 +2,7 @@
** Module : TableMapper.java
** Abstract : Table and field name to converted DMO and property name (bidirectional).
**
-** Copyright (c) 2013-2020, Golden Code Development Corporation.
+** Copyright (c) 2013-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 CA 20131015 Created initial version.
@@ -74,6 +74,12 @@
** OM 20200924 P2JIndexComponent carries multiple information to avoid map lookups for them.
** OM 20201001 Improved DMO manipulation performance by caching slow Property annotation access.
** OM 20201002 Use DmoMeta cached information instead of map lookups.
+** SVL 20201111 Support for null LABEL and COLUMN-LABEL.
+** OM 20201029 Added case sensitive column support.
+** OM 20201110 TableSerializeOptions are volative and cannot be cached.
+** OM 20210122 Temp-table attributes in LegacyFieldInfo can be modified.
+** OM 20210127 Replaced TableMapper.getLegacyName() (now deprecated) triple map lookup with direct access
+** to local dmoMeta.legacyTable.
*/
/*
@@ -163,8 +169,7 @@
* @param
* The type of the key used by the {@link #p2j progress-to-java} map. For permanent
* DMOs, this will be a {@link String}. For temporary DMOs, this will be an
- * {@link Integer} value with the UNIQUE-ID identifying the
- * {@link TempTable} resource.
+ * {@link Integer} value with the {@code UNIQUE-ID} identifying the {@link TempTable} resource.
*/
public abstract class TableMapper
{
@@ -256,29 +261,14 @@
* A buffer instance.
*
* @return The legacy table name.
+ *
+ * @deprecated Use the {@code DmoMeta.legacyTable} directly instead.
*/
+ @Deprecated
public static String getLegacyName(Buffer buffer)
{
RecordBuffer buf = ((BufferImpl) buffer).buffer();
- if (buf.isTemporary())
- {
- TempTable tt = buf.getParentTable();
- String name = tt.name().getValue();
- if (name != null)
- {
- // return the name of the parent table
- return name;
- }
- else
- {
- // name not yet assigned to a static temp table, get it from the mapping
- return getLegacyName(tt);
- }
- }
- else
- {
- return getLegacyName(buf.getDMOInterface());
- }
+ return buf.dmoInfo.legacyTable;
}
/**
@@ -865,40 +855,8 @@
*/
public static String getLegacySchemaName(Class extends DataModelObject> dmoIface)
{
- String schema = DmoMetadataManager.getDmoInfo(dmoIface).getSchema();
-
- return schema + "." + getLegacyName(dmoIface);
- }
-
- /**
- * Get the legacy table name for the given DMO, which is part of a permanent DB.
- *
- * @param dmoIface
- * The DMO interface for a permanent DMO.
- *
- * @return The legacy table name.
- *
- * @throws IllegalStateException
- * If a DMO belonging to the {@link DatabaseManager#TEMP_TABLE_SCHEMA} temp schema
- * is passed as a parameter; for these cases, {@link #getLegacyName(TempTable)}
- * or {@link #getLegacyName(Buffer)} must be used.
- */
- public static String getLegacyName(Class extends DataModelObject> dmoIface)
- {
DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(dmoIface);
- if (dmoInfo.isTempTable())
- {
- String msg = "Legacy table name for a temporary DMO can not be resolved using this API!";
-
- throw new IllegalArgumentException(msg);
- }
-
- synchronized (permanentDBs)
- {
- PermanentTableMapper mapper = locatePerm(dmoInfo.getSchema());
-
- return mapper.legacyName(dmoIface);
- }
+ return dmoInfo.getSchema() + "." + dmoInfo.legacyTable;
}
/**
@@ -931,75 +889,6 @@
}
/**
- * Get the legacy table serialize options for the DMO referenced by the given buffer.
- *
- * @param buffer
- * A buffer instance.
- *
- * @return The serialize options.
- */
- public static TableSerializeOptions getSerializeOptions(Buffer buffer)
- {
- RecordBuffer buf = ((BufferImpl) buffer).buffer();
- if (buf.isTemporary())
- {
- TempTable tt = buf.getParentTable();
- return getSerializeOptions(tt);
- }
- else
- {
- return getSerializeOptions(buf.getDMOInterface());
- }
- }
-
- /**
- * Get the legacy serialize options for the given DMO, which is part of a permanent DB.
- *
- * This API always throws an {@link IllegalStateException}, as serialize options can be defined
- * only for temp-tables.
- *
- * @param dmoIface
- * The DMO interface for a permanent DMO.
- *
- * @return n/a.
- *
- * @throws IllegalStateException
- * Always.
- */
- public static TableSerializeOptions getSerializeOptions(Class extends DataModelObject> dmoIface)
- {
- String msg;
- if (DmoMetadataManager.getDmoInfo(dmoIface).isTempTable())
- {
- msg = "Legacy serialize options for a temporary DMO can not be resolved using this API!";
- }
- else
- {
- msg = "Legacy serialize options are not available for a permanent DMO!";
- }
-
- throw new IllegalArgumentException(msg);
- }
-
- /**
- * Get the legacy serialize options for the given {@link TempTable} resource.
- *
- * @param tt
- * The {@link TempTable} resource.
- *
- * @return The legacy serialize options.
- */
- public static TableSerializeOptions getSerializeOptions(TempTable tt)
- {
- TempTableMapper mapper = locateTemp(tt);
-
- synchronized (mapper)
- {
- return mapper.serializeOptions(tt);
- }
- }
-
- /**
* Get the legacy field name for the given property, which is defined by a DMO part of a
* permanent DB.
*
@@ -1083,7 +972,6 @@
}
}
-
/**
* Get the 4GL name of the extent field represented by the given property.
*
@@ -1212,24 +1100,6 @@
}
/**
- * Get the legacy table name for the given {@link TempTable} resource.
- *
- * @param tt
- * The {@link TempTable} resource.
- *
- * @return The legacy table name.
- */
- public static String getLegacyName(TempTable tt)
- {
- TempTableMapper mapper = locateTemp(tt);
-
- synchronized (mapper)
- {
- return mapper.legacyName(tt);
- }
- }
-
- /**
* Get the legacy field name for the given {@link TempTable} resource and property.
*
* @param tt
@@ -1717,7 +1587,7 @@
String schema = dmoInfo.getSchema();
// - mapping is kept by schema
- // - permamentDBs are kept in a global map
+ // - permanentDBs are kept in a global map
synchronized (permanentDBs)
{
PermanentTableMapper mapper = locatePerm(schema);
@@ -2705,6 +2575,7 @@
{
Integer key = getLegacyKey(tt);
p2j.remove(key);
+ j2p.remove(tt);
}
/**
@@ -2724,22 +2595,6 @@
}
/**
- * Get the legacy serialize options for the given {@link TempTable} resource.
- *
- * @param tt
- * The {@link TempTable} resource.
- *
- * @return The legacy serialize options.
- */
- protected TableSerializeOptions serializeOptions(TempTable tt)
- {
- Integer key = getLegacyKey(tt);
- LegacyTableInfo info = p2j.get(key);
-
- return info.serializeOptions;
- }
-
- /**
* Get the mapping of temp-table IDs to {@link LegacyTableInfo} instances.
*
* @return Map of temp-table IDs to table information objects.
@@ -2940,9 +2795,6 @@
/** The DMO implementation class. */
private final Class extends Record> dmoClass;
- /** The temp-table serialize options. */
- private final TableSerializeOptions serializeOptions;
-
/** true if the table was defined with LIKE-SEQUENTIAL option. */
private final boolean likeSequential;
@@ -2956,7 +2808,7 @@
* @param dmoInfo
* Object which contains metadata about the DMO.
*
- * @see #loadFields(Class)
+ * @see #loadFields(DmoMeta)
*/
private LegacyTableInfo(DmoMeta dmoInfo)
{
@@ -2965,11 +2817,7 @@
this.dmoClass = dmoInfo.getImplementationClass();
this.validation = dmoInfo.hasValidation;
this.likeSequential = dmoInfo.isLikeSequential;
- this.serializeOptions = new TableSerializeOptions(dmoInfo.serializeName,
- dmoInfo.xmlNodeName,
- dmoInfo.namespaceUri,
- dmoInfo.namespacePrefix);
- loadFields(dmoIface);
+ loadFields(dmoInfo);
List fieldList = new ArrayList<>(j2p.values());
Collections.sort(fieldList);
@@ -3032,15 +2880,14 @@
/**
* Load all fields from the specified class and add their mappings.
*
- * @param dmoIface
- * The DMO interface or a composite class with the extent fields.
+ * @param dmoInfo
+ * The DMO data for the interface or a composite class with the extent fields.
*
* @throws NullPointerException
* If a DMO property does not have a {@link Property} annotation.
*/
- private void loadFields(Class extends DataModelObject> dmoIface)
+ private void loadFields(DmoMeta dmoInfo)
{
- DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(dmoIface);
for (Iterator it = dmoInfo.getFields(true); it.hasNext(); )
{
Property fieldAnn = it.next();
@@ -3159,31 +3006,34 @@
boolean firstSeen = fieldInfo == null;
if (firstSeen || fieldInfo.fieldId != fieldId)
{
- fieldInfo = new LegacyFieldInfo(columnLabel,
- fieldId,
- format,
- help,
- initial,
- decimals,
- javaName,
- label,
- legacyName,
- mandatory,
- extent,
- order,
- position,
- original,
- validateMessage,
- validateExpression,
- (Class extends BaseDataType>) dataType,
- initialValue,
- serializeHidden,
- serializeName,
- xmlDataType,
- xmlNodeName,
- xmlNodeType,
- like,
- codePage);
+ fieldInfo = new LegacyFieldInfo(
+ dmoInfo.tempTable,
+ columnLabel,
+ fieldId,
+ format,
+ help,
+ initial,
+ decimals,
+ javaName,
+ label,
+ legacyName,
+ mandatory,
+ extent,
+ order,
+ position,
+ original,
+ validateMessage,
+ validateExpression,
+ (Class extends BaseDataType>) dataType,
+ initialValue,
+ serializeHidden,
+ serializeName,
+ xmlDataType,
+ xmlNodeName,
+ xmlNodeType,
+ like,
+ codePage,
+ fieldAnn.caseSensitive);
}
if (firstSeen) // for extent field store the first representative info only
@@ -3208,31 +3058,34 @@
javaNames.put(extentIndex, fieldInfo);
if (extents.get(original) == null)
{
- LegacyFieldInfo extentInfo = new LegacyFieldInfo(columnLabel,
- fieldId,
- format,
- help,
- initial,
- decimals,
- original,
- label,
- legacyName,
- mandatory,
- extent,
- order,
- position,
- "",
- validateMessage,
- validateExpression,
- (Class extends BaseDataType>) dataType,
- initialValue,
- serializeHidden,
- serializeName,
- xmlDataType,
- xmlNodeName,
- xmlNodeType,
- like,
- codePage);
+ LegacyFieldInfo extentInfo = new LegacyFieldInfo(
+ dmoInfo.tempTable,
+ columnLabel,
+ fieldId,
+ format,
+ help,
+ initial,
+ decimals,
+ original,
+ label,
+ legacyName,
+ mandatory,
+ extent,
+ order,
+ position,
+ "",
+ validateMessage,
+ validateExpression,
+ (Class extends BaseDataType>) dataType,
+ initialValue,
+ serializeHidden,
+ serializeName,
+ xmlDataType,
+ xmlNodeName,
+ xmlNodeType,
+ like,
+ codePage,
+ fieldAnn.caseSensitive);
extents.put(original, extentInfo);
}
}
@@ -3386,7 +3239,7 @@
private final String fieldLegacyName;
/** Determines if this component has descending sorting. */
- private boolean effectiveDescending;
+ private final boolean effectiveDescending;
/**
* Create a new index component information container, holding field name and sorting
@@ -3461,8 +3314,8 @@
private final String initial;
/** DECIMALS attribute of this field. */
- private final int decimals;
-
+ private int decimals;
+
/** The associated DMO property name. */
private final String javaName;
@@ -3473,13 +3326,13 @@
private final String legacyName;
/** EXTENT attribute of the field (from the legacy schema). */
- private int extent;
+ private final int extent;
/** ORDER attribute of the field (from the legacy schema). */
- private int order;
+ private final int order;
/** POSITION attribute of the field (from the legacy schema). */
- private int position;
+ private final int position;
/** LITERAL-QUESTION attribute of this field. */
private boolean literalQuestion = false;
@@ -3504,20 +3357,30 @@
/** Options needed when serializing temp-table data to/from external media */
private final SerializeOptions serializeOptions;
-
+
/**
* Fully qualified name of the source field from which the field definitions were copied
* using LIKE option.
*/
private final String like;
-
+
/** Code page of the CLOB field. */
private final String codePage;
+ /** Flags case-sensitive character fields. */
+ private final boolean caseSensitive;
+
+ /** Flags mutable objects. */
+ private final boolean mutable;
+
/**
- * Create a new field information container, holding the legacy and converted names and
- * field attributes.
+ * Create a new field information container, holding the legacy and converted names and field
+ * attributes.
*
+ * @param mutable
+ * If this structure is mutable. This is the case of temp-tables, which allow some of theirs
+ * field properties to be changed programmatically, at runtime. Only the mutable attributes
+ * have setters and their implementation must check this flag.
* @param columnLabel
* COLUMN-LABEL attribute.
* @param fieldId
@@ -3569,40 +3432,45 @@
* copied using LIKE option.
* @param codePage
* Code page of the CLOB field.
+ * @param caseSensitive
+ * Flags case-sensitive character fields.
*/
- public LegacyFieldInfo(String columnLabel,
- Integer fieldId,
- String format,
- String help,
- String initial,
- int decimals,
- String javaName,
- String label,
- String legacyName,
- boolean mandatory,
- int extent,
- int order,
- int position,
- String original,
- String validateMessage,
- String validateExpression,
- Class extends BaseDataType> dataType,
- BaseDataType initialValue,
- boolean serializeHidden,
- String serializeName,
- String xmlDataType,
- String xmlNodeName,
- String xmlNodeType,
- String like,
- String codePage)
+ private LegacyFieldInfo(boolean mutable,
+ String columnLabel,
+ Integer fieldId,
+ String format,
+ String help,
+ String initial,
+ int decimals,
+ String javaName,
+ String label,
+ String legacyName,
+ boolean mandatory,
+ int extent,
+ int order,
+ int position,
+ String original,
+ String validateMessage,
+ String validateExpression,
+ Class extends BaseDataType> dataType,
+ BaseDataType initialValue,
+ boolean serializeHidden,
+ String serializeName,
+ String xmlDataType,
+ String xmlNodeName,
+ String xmlNodeType,
+ String like,
+ String codePage,
+ boolean caseSensitive)
{
- this.columnLabel = columnLabel.intern();
+ this.mutable = mutable;
+ this.columnLabel = columnLabel == null ? null : columnLabel.intern();
this.fieldId = fieldId;
this.help = help.intern();
this.initial = initial == null ? null : initial.intern();
this.decimals = decimals;
this.javaName = javaName.intern();
- this.label = label.intern();
+ this.label = label == null ? null : label.intern();
this.legacyName = legacyName.intern();
this.mandatory = mandatory;
this.extent = extent;
@@ -3611,6 +3479,7 @@
this.original = original.intern();
this.validateMessage = validateMessage;
this.validateExpression = validateExpression;
+ this.caseSensitive = caseSensitive;
String defaultFormat;
@@ -3862,6 +3731,44 @@
}
/**
+ * Checks whether this is a case-sensitive character field.
+ *
+ * @return {@code true} only if this is a case-sensitive character field.
+ */
+ public boolean isCaseSensitive()
+ {
+ return caseSensitive;
+ }
+
+ /**
+ * Obtain the precision for decimal fields.
+ *
+ * @return the precision for decimal fields.
+ */
+ public int getDecimals()
+ {
+ return decimals;
+ }
+
+ /**
+ * Sets the precision for decimal fields. Only for mutable objects.
+ *
+ * @param decimals
+ * The new precision for decimal fields.
+ *
+ * @throws IllegalStateException
+ * if the object is not declared as mutable.
+ */
+ public void setDecimals(int decimals)
+ {
+ if (!mutable)
+ {
+ throw new IllegalStateException("Object not mutable");
+ }
+ this.decimals = decimals;
+ }
+
+ /**
* Compare this field info object to another, based on its natural order. Natural order is
* determined by field IDs.
*
@@ -3874,5 +3781,24 @@
{
return this.fieldId - o.fieldId;
}
+
+ /**
+ * Obtain a short sreing representation of the object used in debugging.
+ *
+ * @return a short sreing representation of the object used in debugging.
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder("LFI{");
+ sb.append(legacyName).append("/").append(javaName);
+ sb.append("(").append(fieldId).append(")").append(":").append(dataType.getSimpleName());
+ if (extent != 0)
+ {
+ sb.append("[").append(extent).append("]");
+ }
+ sb.append('}');
+ return sb.toString();
+ }
}
}
=== modified file 'src/com/goldencode/p2j/persist/TableWrapper.java'
--- src/com/goldencode/p2j/persist/TableWrapper.java 2020-09-14 12:05:37 +0000
+++ src/com/goldencode/p2j/persist/TableWrapper.java 2021-01-13 21:04:41 +0000
@@ -1,11 +1,10 @@
/*
** Module : TableWrapper.java
-** Abstract : A wrapper for table result sets, used to serialize a table data and send it to a
-* remote side.
+** Abstract : A wrapper for table result sets, used to serialize a table data and send it to a remote side.
**
** Copyright (c) 2013-2020, Golden Code Development Corporation.
**
-** -#- -I- --Date-- ---------------------------------Description----------------------------------
+** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 CA 20130620 Created initial version.
** 002 SVL 20140611 Fixed NPE in writeExternal when no result set is transferred. Added tableName.
** 003 SVL 20140797 Added tableHandle field.
@@ -19,6 +18,7 @@
** CA 20200514 In some cases, BLOB fields are already MemoryBuffer.
** 008 ECF 20200906 New ORM implementation.
** 009 IAS 20200908 Rework (de)serialization.
+** OM 20201120 Added implementation of XML-NODE-NAME and ERROR-STRING attributes
*/
/*
@@ -136,6 +136,12 @@
/** The XML prefix for this table definition. */
private String xmlPrefix = null;
+ /** The XML-NODE-NAME attribute for this table definition. */
+ private String xmlNodeName = null;
+
+ /** The ERROR-STRING attribute for this table definition. */
+ private String errorString = null;
+
/**
* A string with all indexes of this table definition encoded as a {@code String}.
*
@@ -377,6 +383,48 @@
}
/**
+ * Obtain the XML node name for this table definition.
+ *
+ * @return the XML node name for this table definition.
+ */
+ public String getXmlNodeName()
+ {
+ return xmlNodeName;
+ }
+
+ /**
+ * Sets the XML node name for this table definition.
+ *
+ * @param xmlNodeName
+ * XML prefix for this table definition.
+ */
+ public void setXmlNodeName(String xmlNodeName)
+ {
+ this.xmlNodeName = xmlNodeName;
+ }
+
+ /**
+ * Obtain the ERROR-STRING attribute for this table definition.
+ *
+ * @return the ERROR-STRING attribute for this table definition.
+ */
+ public String getErrorString()
+ {
+ return errorString;
+ }
+
+ /**
+ * Sets the ERROR-STRING attribute for this table definition.
+ *
+ * @param err
+ * ERROR-STRING attribute for this table definition.
+ */
+ public void setErrorString(String err)
+ {
+ this.errorString = err;
+ }
+
+ /**
* Provides an iterator over the read {@link #properties}. This is used to access the data
* sent by a remote side, via {@link #readExternal}. Must not be called when {@link #resultSet}
* is set.
@@ -741,8 +789,8 @@
Buffer defBuff = (Buffer) table.defaultBufferHandle().getResource();
this.indexes = TableMapper.getLegacyIndexInfo(defBuff);
this.numIndexes = indexes.split(".").length;
- this.xmlns = defBuff.getNamespaceURI().toStringMessage();
- this.xmlPrefix = defBuff.getNamespacePrefix().toStringMessage();
+ this.xmlns = defBuff.namespaceURI().toStringMessage();
+ this.xmlPrefix = defBuff.namespacePrefix().toStringMessage();
}
/**
=== modified file 'src/com/goldencode/p2j/persist/TargetData.java'
--- src/com/goldencode/p2j/persist/TargetData.java 2020-09-10 09:54:53 +0000
+++ src/com/goldencode/p2j/persist/TargetData.java 2021-01-29 00:53:41 +0000
@@ -4,7 +4,7 @@
**
** Copyright (c) 2017-2020, Golden Code Development Corporation.
**
-** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 ECF 20170820 Created initial version.
** 002 CA 20170830 Added missing c'tor.
** 003 ECF 20171008 Rolled back #002. Implemented file as an output resource.
@@ -14,6 +14,8 @@
** 007 CA 20190720 byte[] must be converted to String before copying it.
** CA 20190811 Added memptr support.
** 008 CA 20200910 Fixed Json c'tors - these can be any object, the type is checked at runtime.
+** OM 20201120 Implemented Closable interface. The file stream is lazily opened.
+** OM 20210120 Added full type support, including validation and error handling.
*/
/*
@@ -29,39 +31,39 @@
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
-**
+**
** Additional terms under GNU Affero GPL version 3 section 7:
-**
+**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
-**
+**
** 0. Attribution Requirement.
-**
+**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
-**
+**
** 1. No License To Use Trademarks.
-**
+**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
-**
+**
** 2. No Misrepresentation of Affiliation.
-**
+**
** You may not represent yourself as Golden Code Development Corporation or FWD.
-**
+**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
-**
+**
** 3. No Misrepresentation of Source or Origin.
-**
+**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
@@ -72,9 +74,8 @@
package com.goldencode.p2j.persist;
import java.io.*;
-
-import com.goldencode.p2j.oo.json.objectmodel.JsonConstruct;
-import com.goldencode.p2j.oo.lang._BaseObject_;
+import java.util.*;
+import com.goldencode.p2j.persist.serial.*;
import com.goldencode.p2j.util.*;
/**
@@ -82,234 +83,315 @@
* or data set member buffer.
*/
public class TargetData
+implements Serializator
{
/** Normalized output stream */
private OutputStream stream;
- /**
- * Constructor.
- *
- * @param type
- * {@code FILE} or {@code STREAM}.
- * @param target
- * Name of a file or stream to which data is written.
- */
- public TargetData(character type, character target)
- {
- initializeRemote(type.toStringMessage(), target.toStringMessage());
- }
-
- /**
- * Constructor.
- *
- * @param type
- * {@code FILE} or {@code STREAM}.
- * @param target
- * Name of a file or stream to which data is written.
- */
- public TargetData(String type, character target)
- {
- initializeRemote(type, target.toStringMessage());
- }
-
- /**
- * Constructor.
- *
- * @param type
- * {@code FILE} or {@code STREAM}.
- * @param target
- * Name of a file or stream to which data is written.
- */
- public TargetData(character type, String target)
- {
- initializeRemote(type.toStringMessage(), target);
- }
-
- /**
- * Constructor.
- *
- * @param type
- * {@code FILE} or {@code STREAM}.
- * @param target
- * Name of a file or stream to which data is written.
+ /** The file name if such destination was created. */
+ private String fileName = null;
+
+ /**
+ * Constant used to identify the calls from WRITE-XML method. Also used to store the supported stream types
+ * for this method.
+ */
+ public static final String TD_WRITE_XML = "WRITE-XML";
+
+ /**
+ * Constant used to identify the calls from WRITE-XMLSCHEMA method. Also used to store the supported stream
+ * types for this method.
+ */
+ public static final String TD_WRITE_XMLSCHEMA = "WRITE-XMLSCHEMA";
+
+ /**
+ * Constant used to identify the calls from WRITE-JSON method. Also used to store the supported stream
+ * types for this method.
+ */
+ public static final String TD_WRITE_JSON = "WRITE-JSON";
+
+ /**
+ * Constant used to identify the calls from SERIALIZE-ROW method. Also used to store the supported stream
+ * types for this method.
+ */
+ public static final String TD_SERIALIZE_ROW = "SERIALIZE-ROW";
+
+ /* The initialization of static member data. */
+ private static final Map supportedStreams = new HashMap<>();
+ static
+ {
+ supportedStreams.put(TD_WRITE_XML,
+ SER_FILE | SER_MEMPTR | SER_HANDLE | SER_LONGCHAR);
+ supportedStreams.put(TD_WRITE_XMLSCHEMA,
+ SER_FILE | SER_MEMPTR | SER_HANDLE | SER_LONGCHAR);
+ supportedStreams.put(TD_WRITE_JSON,
+ SER_FILE | SER_MEMPTR | SER_HANDLE | SER_LONGCHAR | SER_JSON_ARRAY | SER_JSON_OBJECT);
+ supportedStreams.put(TD_SERIALIZE_ROW,
+ SER_FILE | SER_STREAM | SER_STREAM_HANDLE | SER_MEMPTR | SER_LONGCHAR | SER_JSON_OBJECT);
+ }
+
+ private final String origType;
+ private final BaseDataType origDst;
+
+ /**
+ * The constructor saves the configured parameters but does not process them in any way. The validation
+ * will be executed later, when the calling method is in progress.
+ *
+ * @param type
+ * The source stream type.
+ * @param target
+ * The reference to resource or the file name.
+ */
+ public TargetData(character type, BaseDataType target)
+ {
+ this((type == null) ? null : type.toJavaType(), target);
+ }
+
+ /**
+ * The constructor saves the configured parameters but does not process them in any way. The validation
+ * will be executed later, when the calling method is in progress.
+ *
+ * @param type
+ * The source stream type.
+ * @param target
+ * The reference to resource or the file name.
+ */
+ public TargetData(String type, BaseDataType target)
+ {
+ origType = type;
+ origDst = target;
+ }
+
+ /**
+ * The constructor saves the configured parameters but does not process them in any way. The validation
+ * will be executed later, when the calling method is in progress.
+ *
+ * @param type
+ * The source stream type.
+ * @param target
+ * The file name.
*/
public TargetData(String type, String target)
{
- initializeRemote(type, target);
- }
-
- /**
- * Constructor.
- *
- * @param target
- * Pointer to an area of memory into which data is written.
- */
- public TargetData(memptr target)
- {
- // TODO: better way to do this than overriding close()? Overriding flush() may cause to
- // many assigns to the target longchar
- stream = new ByteArrayOutputStream()
- {
- @Override
- public void close()
- throws IOException
- {
- flush();
- super.close();
- byte[] buf = toByteArray();
- target.assign(buf);
- }
- };
- }
-
- /**
- * Constructor.
- *
- * @param type
- * {@code STREAM-HANDLE} or {@code HANDLE}.
- * @param target
- * Handle of a stream object or X-document/X-noderef object into which to write data.
- */
- public TargetData(character type, handle target)
- {
- initializeRemote(type.toStringMessage(), target);
- }
-
- /**
- * Constructor.
- *
- * @param type
- * {@code STREAM-HANDLE} or {@code HANDLE}.
- * @param target
- * Handle of a stream object or X-document/X-noderef object into which to write data.
- */
- public TargetData(String type, handle target)
- {
- initializeRemote(type, target);
- }
-
- /**
- * Constructor.
- *
- * @param type
- * {@code STREAM-HANDLE} or {@code HANDLE}.
- * @param target
- * The target JsonObject or JsonArray instance.
- */
- public TargetData(character type, object extends _BaseObject_> target)
- {
- UnimplementedFeature.todo("BUFFER:{SERIALIZE-ROW|WRITE-JSON}(JsonObject|JsonArray) is not implemented.");
- }
-
- /**
- * Constructor.
- *
- * @param type
- * {@code STREAM-HANDLE} or {@code HANDLE}.
- * @param target
- * The target JsonObject or JsonArray instance.
- */
- public TargetData(String type, object extends _BaseObject_> target)
- {
- UnimplementedFeature.todo("BUFFER:{SERIALIZE-ROW|WRITE-JSON}(JsonObject|JsonArray) is not implemented.");
- }
-
- /**
- * Constructor.
- *
- * @param target
- * Variable into which to write data.
- */
- public TargetData(longchar target)
- {
- // TODO: better way to do this than overriding close()? Overriding flush() may cause to
- // many assigns to the target longchar
- stream = new ByteArrayOutputStream()
- {
- @Override
- public void close()
- throws IOException
- {
- flush();
- super.close();
- byte[] buf = toByteArray();
- target.assign(new String(buf));
- }
- };
+ origType = type;
+ origDst = new character(target);
+ }
+
+ /**
+ * Configures the supported resource types for the current usage. The caller passes in its name and the
+ * method does the selection. It also does the verification whether the already configured parameters (in
+ * the constructor) are valid and eventual raise the error condition.
+ *
+ * @param method
+ * The identifier for the method using the source stream. It is used to get the set of acceptable
+ * stream types to check against them.
+ * @param widgetType
+ * The parent widget. Only used for composing the error messages.
+ *
+ * @return {@code true} if the parameters configured in constructor seems correct. This method does not
+ * check the content, only the variable types. If there is not a match with the possible streams
+ * supported by the {@code method}, {@code false} is returned, after eventually raising a specific
+ * error condition.
+ */
+ public boolean configureSupportedTargets(String method, String widgetType)
+ {
+ boolean isJson = method.contains("JSON");
+ if (origType == null)
+ {
+ ErrorManager.recordOrShowError(5442, method, "character");
+ // Invalid datatype for argument to method ''. Expecting ''
+ return false;
+ }
+
+ if (origDst == null || origDst.isUnknown())
+ {
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ return false;
+ }
+
+ // [method] must always be one of the supported methods
+ int supportedDestinations = supportedStreams.get(method);
+
+ switch (origType.toUpperCase())
+ {
+ case TYPE_FILE:
+ if ((supportedDestinations & SER_FILE) != 0)
+ {
+ if (!(origDst instanceof character))
+ {
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ return false;
+ }
+ return true;
+ }
+ break;
+
+ case TYPE_STREAM:
+ if ((supportedDestinations & SER_STREAM) != 0)
+ {
+ return true;
+ }
+ break;
+
+ case TYPE_STREAM_HANDLE:
+ if ((supportedDestinations & SER_STREAM_HANDLE) != 0)
+ {
+ return true;
+ }
+ break;
+
+ case TYPE_MEMPTR:
+ if ((supportedDestinations & SER_MEMPTR) != 0)
+ {
+ if (!(origDst instanceof memptr))
+ {
+ break;
+ }
+ return true;
+ }
+ break;
+
+ case TYPE_HANDLE:
+ if (!isJson)
+ {
+ ErrorManager.recordOrShowError(13186);
+ // Handle type not valid as WRITE-XML target. (13186)
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ return false;
+ }
+ break;
+
+ case TYPE_LONGCHAR:
+ if ((supportedDestinations & SER_LONGCHAR) != 0)
+ {
+ if (!(origDst instanceof longchar))
+ {
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ return false;
+ }
+ return true;
+ }
+ break;
+
+ case TYPE_JSON_OBJECT:
+ if ((supportedDestinations & SER_JSON_OBJECT) != 0)
+ {
+ return true;
+ }
+ break;
+
+ case TYPE_JSON_ARRAY:
+ if ((supportedDestinations & SER_JSON_ARRAY) != 0)
+ {
+ return true;
+ }
+ break;
+ }
+
+ if (isJson)
+ {
+ ErrorManager.recordOrShowError(15344, origType, method);
+ // Invalid mode '' for . (15344)
+ }
+ else
+ {
+ ErrorManager.recordOrShowError(13185, origType);
+ // Invalid target-type for WRITE-XML: . (13185)
+ ErrorManager.recordOrShowError(4065, method, widgetType);
+ // **The attribute on the has invalid arguments. (4065)
+ }
+ return false;
}
/**
* Get the output stream representing the target resource to which data will be written.
* This may be a remote or a local resource.
- *
+ *
* @return Output stream.
*/
- public OutputStream getOutputStream()
+ @Override
+ public OutputStream getStream()
{
- return stream;
+ if (stream != null)
+ {
+ return stream;
+ }
+
+ switch (origType.toUpperCase())
+ {
+ case TYPE_JSON_ARRAY:
+ case TYPE_JSON_OBJECT:
+ UnimplementedFeature.todo("BUFFER:{SERIALIZE-ROW|WRITE-JSON}(JsonObject|JsonArray) is not implemented.");
+ return null;
+
+ case TYPE_STREAM:
+ case TYPE_STREAM_HANDLE:
+ case TYPE_FILE:
+ if (origDst instanceof character)
+ {
+ fileName = ((character) origDst).toJavaType();
+ stream = new OutputStreamWrapper(StreamFactory.openFileStream(fileName, true, false));
+ return stream;
+ }
+ return null;
+
+ case TYPE_LONGCHAR:
+ if (origDst instanceof longchar)
+ {
+ longchar dest = (longchar) origDst;
+ stream = new ByteArrayOutputStream()
+ {
+ @Override
+ public void close()
+ throws IOException
+ {
+ flush();
+ super.close();
+ dest.assign(this.toString());
+ }
+ };
+ return stream;
+ }
+ return null;
+
+ case TYPE_MEMPTR:
+ if (origDst instanceof memptr)
+ {
+ memptr dest = (memptr) origDst;
+ stream = new ByteArrayOutputStream()
+ {
+ @Override
+ public void close()
+ throws IOException
+ {
+ flush();
+ super.close();
+ dest.assign(this.toByteArray());
+ }
+ };
+ return stream;
+ }
+ return null;
+ }
+
+ return null;
}
/**
* Close the output stream underlying this data source.
- *
+ *
* @throws IOException
* if there is an error closing the stream.
*/
- void close()
+ public void close()
throws IOException
{
- stream.close();
- }
-
- /**
- * Initialize a remote file or stream resource as an output stream.
- *
- * The stream resource is unimplemented currently.
- *
- * @param type
- * Type of output resource: FILE or STREAM.
- * @param name
- * The name of the resource (path for a file; name for a stream).
- */
- private void initializeRemote(String type, String name)
- {
- switch(type.toUpperCase())
- {
- case "FILE":
- stream = new OutputStreamWrapper(StreamFactory.openFileStream(name, true, false));
- break;
- case "STREAM":
- // TODO: get the stream and wrap it in OutputStreamWrapper
- UnimplementedFeature.missing("TargetData STREAM support");
- break;
- default:
- throw new IllegalArgumentException("Data target type must be FILE or STREAM");
- }
- }
-
- /**
- * Initialize a remote stream handle or handle resource as an output stream.
- *
- * Both types are unimplemented features currently.
- *
- * @param type
- * Type of output resource: STREAM-HANDLE or HANDLE.
- * @param target
- * The handle to the resource.
- */
- private void initializeRemote(String type, handle target)
- {
- switch(type.toUpperCase())
- {
- case "STREAM-HANDLE":
- UnimplementedFeature.missing("TargetData STREAM-HANDLE support");
- break;
- case "HANDLE":
- UnimplementedFeature.missing("TargetData HANDLE support");
- break;
- default:
- throw new IllegalArgumentException(
- "Data target type must be STREAM-HANDLE or HANDLE");
+ if (stream != null)
+ {
+ stream.close();
}
}
}
=== modified file 'src/com/goldencode/p2j/persist/TempRecord.java'
--- src/com/goldencode/p2j/persist/TempRecord.java 2020-08-28 08:40:21 +0000
+++ src/com/goldencode/p2j/persist/TempRecord.java 2020-12-19 00:59:30 +0000
@@ -9,6 +9,7 @@
** OM 20200110 Added constants and method implementations.
** CA 20200714 Refactored to keep the reserved properties in the record's data, to be persisted in the
** table.
+** 002 OM 20201218 Fixed implementation for error and rejected attributes/hidden fields.
*/
/*
@@ -250,7 +251,7 @@
* @see #_ERROR_FLAG
*/
@Override
- public final Integer _errorFlag()
+ public final Integer _errorFlags()
{
return (Integer) data[_ERROR_FLAG_DATA_INDEX];
}
@@ -264,7 +265,7 @@
* @see #_ERROR_FLAG
*/
@Override
- public final void _errorFlag(Integer flag)
+ public final void _errorFlags(Integer flag)
{
int savedActiveOffset = this.activeOffset;
try
=== modified file 'src/com/goldencode/p2j/persist/TempTable.java'
--- src/com/goldencode/p2j/persist/TempTable.java 2020-10-02 22:45:29 +0000
+++ src/com/goldencode/p2j/persist/TempTable.java 2021-01-15 23:08:08 +0000
@@ -2,7 +2,7 @@
** Module : TempTable.java
** Abstract : Declaration of some dynamic TEMP-TABLES specific features (attributes, methods).
**
-** Copyright (c) 2013-2020, Golden Code Development Corporation.
+** Copyright (c) 2013-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM 20130129 Created initial version.
@@ -35,7 +35,9 @@
** 023 OM 20191031 Removed method signatures pushed to super interface.
** 024 ECF 20200906 New ORM implementation.
** 025 AIL 20200911 Added Dereferenceable interface.
-** 026 OM 20201002 Use DmoMeta cached information instead of map lookups.
+** OM 20201002 Use DmoMeta cached information instead of map lookups.
+** OM 20201120 Added declarations for SCHEMA-MARSHALL related accessors.
+** OM 20210106 Fixed 2nd parameter of TEMP-TABLE-PREPARE.
*/
/*
@@ -108,6 +110,8 @@
Clearable,
CommonHandleChain,
CreateLike,
+ DataSourceModifiable,
+ Deletable,
Dereferenceable,
DynamicResource,
EmptyTempTable,
@@ -116,10 +120,14 @@
InstantiatingProcedure,
JsonData,
Nameable,
+ NamedSerializable,
+ NamespaceURI,
+ Rejectable,
TempTableDuplicator,
UndoStateProvider,
UniqueID,
- XmlData
+ XmlData,
+ XmlNode
{
/** The type of this buffer from the {@code DataSet} changes point of view. */
enum BeforeType
@@ -140,6 +148,20 @@
SIMPLE
}
+ /** Constant for SCHEMA-MARSHAL attribute: the full schema is serialized with table parameter. */
+ public static final String SCHEMA_MARSHAL_FULL = "FULL";
+
+ /**
+ * Constant for SCHEMA-MARSHAL attribute: minimal schema is marshalled. When in use, the field names, data
+ * types and extents, and temp-table ERROR-STRING are serialized with table parameter, but index
+ * descriptions and other field information (label, help, field validation expression, and so on (?)) are
+ * not.
+ */
+ public static final String SCHEMA_MARSHAL_MIN = "MIN";
+
+ /** Constant for SCHEMA-MARSHAL attribute: no schema is marshalled (just table data). */
+ public static final String SCHEMA_MARSHAL_NONE = "NONE";
+
/**
* Adds a field with the specified properties to the temp-table.
* This method is the P2J equivalent of ADD-NEW-FIELD method
@@ -149,7 +171,7 @@
* The name of the field to be created in the temp-table.
* @param type
* The data type of the specified field.
- *
+ *
* @return true on success.
*/
@LegacyMethod(name = "ADD-NEW-FIELD")
@@ -166,12 +188,12 @@
* The data type of the specified field.
* @param extent
* An integer expression specifying the extent of an array.
- *
+ *
* @return true on success.
*/
@LegacyMethod(name = "ADD-NEW-FIELD")
public logical addNewField(character name, character type, integer extent);
-
+
/**
* Adds a field with the specified properties to the temp-table.
* This method is the P2J equivalent of ADD-NEW-FIELD method
@@ -191,7 +213,7 @@
*/
@LegacyMethod(name = "ADD-NEW-FIELD")
public logical addNewField(character name, character type, integer extent, character format);
-
+
/**
* Adds a field with the specified properties to the temp-table.
* This method is the P2J equivalent of ADD-NEW-FIELD method
@@ -210,7 +232,7 @@
* An expression that evaluates to the initial value of the
* defined field.
* TODO: this method will probably be overloaded because of this.
- *
+ *
* @return true on success.
*/
@LegacyMethod(name = "ADD-NEW-FIELD")
@@ -219,7 +241,7 @@
integer extent,
character format,
BaseDataType initial);
-
+
/**
* Adds a field with the specified properties to the temp-table.
* This method is the P2J equivalent of ADD-NEW-FIELD method
@@ -487,7 +509,7 @@
*/
@LegacyMethod(name = "ADD-LIKE-INDEX")
public logical addIndexLike(character name, character sourceName, handle sourceBuffer);
-
+
/**
* Signals that all the field and index definitions for a temp-table
* have been provided. After the call to this method no fields and indexes
@@ -503,7 +525,7 @@
*/
@LegacyMethod(name = "TEMP-TABLE-PREPARE")
public logical tempTablePrepare(String name);
-
+
/**
* Signals that all the field and index definitions for a temp-table have been provided. After
* the call to this method no fields and indexes can be added to this temporary table.
@@ -525,8 +547,7 @@
* This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
*
* @param name
- * Temp-table name to be used in subsequent query statements which refer to this
- * temp-table.
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
* @param before
* Create the {@code BEFORE-TABLE} also if {@code true}.
*
@@ -541,8 +562,37 @@
* This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
*
* @param name
- * Temp-table name to be used in subsequent query statements which refer to this
- * temp-table.
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
+ * @param before
+ * Create the {@code BEFORE-TABLE} also if {@code true}.
+ *
+ * @return {@code true} on success.
+ */
+ @LegacyMethod(name = "TEMP-TABLE-PREPARE")
+ public logical tempTablePrepare(String name, logical before);
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After
+ * the call to this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
+ * @param name
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
+ * @param before
+ * Create the {@code BEFORE-TABLE} also if {@code true}.
+ *
+ * @return {@code true} on success.
+ */
+ @LegacyMethod(name = "TEMP-TABLE-PREPARE")
+ public logical tempTablePrepare(character name, boolean before);
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After
+ * the call to this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
+ * @param name
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
* @param before
* Create the {@code BEFORE-TABLE} also if {@code true}.
*
@@ -1062,4 +1112,86 @@
*/
@LegacyAttribute(name = "ORIGIN-HANDLE")
public handle getOriginHandle();
+
+ /**
+ * Implements the {@code NO-SCHEMA-MARSHAL} attribute getter.
+ *
+ * @return {@code true} when {@code SCHEMA-MARSHAL} attribute is set to {@code "NONE"}.
+ */
+ @LegacyAttribute(name = "NO-SCHEMA-MARSHAL")
+ public logical isNoSchemaMarshal();
+
+ /**
+ * Sets the {@code SCHEMA-MARSHAL} attribute to {@code "NO"} value when {@code on = true} or
+ * back to default value {@code on = false}.
+ *
+ * @param on
+ * The new value of {@code SCHEMA-MARSHAL} attribute.
+ */
+ @LegacyAttribute(name = "NO-SCHEMA-MARSHAL", setter = true)
+ public void setNoSchemaMarshal(boolean on);
+
+ /**
+ * Sets the {@code SCHEMA-MARSHAL} attribute to {@code "NO"} value when {@code on = true} or
+ * back to default value {@code on = false}.
+ *
+ * @param on
+ * The new value of {@code SCHEMA-MARSHAL} attribute.
+ */
+ @LegacyAttribute(name = "NO-SCHEMA-MARSHAL", setter = true)
+ public void setNoSchemaMarshal(logical on);
+
+ /**
+ * Implements the {@code MIN-SCHEMA-MARSHAL} attribute getter.
+ *
+ * @return {@code true} when {@code SCHEMA-MARSHAL} attribute is set to {@code "MIN"}.
+ */
+ @LegacyAttribute(name = "MIN-SCHEMA-MARSHAL")
+ public logical isMinSchemaMarshal();
+
+ /**
+ * Sets the {@code SCHEMA-MARSHAL} attribute to {@code "MIN"} value when {@code on = true} or
+ * back to default value {@code on = false}.
+ *
+ * @param on
+ * The new value of {@code MIN-SCHEMA-MARSHAL} attribute.
+ */
+ @LegacyAttribute(name = "MIN-SCHEMA-MARSHAL", setter = true)
+ public void setMinSchemaMarshal(boolean on);
+
+ /**
+ * Sets the {@code SCHEMA-MARSHAL} attribute to {@code "MIN"} value when {@code on = true} or
+ * back to default value {@code on = false}.
+ *
+ * @param on
+ * The new value of {@code MIN-SCHEMA-MARSHAL} attribute.
+ */
+ @LegacyAttribute(name = "MIN-SCHEMA-MARSHAL", setter = true)
+ public void setMinSchemaMarshal(logical on);
+
+ /**
+ * Obtain the current value of {@code SCHEMA-MARSHAL} attribute.
+ *
+ * @return the current value of {@code SCHEMA-MARSHAL} attribute.
+ */
+ @LegacyAttribute(name = "SCHEMA-MARSHAL")
+ public character getSchemaMarshal();
+
+ /**
+ * Sets a new value for {@code SCHEMA-MARSHAL} attribute.
+ *
+ * @param level
+ * the new value or {@code SCHEMA-MARSHAL} attribute.
+ */
+ @LegacyAttribute(name = "SCHEMA-MARSHAL", setter = true)
+ public void setSchemaMarshal(String level);
+
+ /**
+ * Sets a new value for {@code SCHEMA-MARSHAL} attribute.
+ *
+ * @param level
+ * the new value or {@code SCHEMA-MARSHAL} attribute.
+ */
+ @LegacyAttribute(name = "SCHEMA-MARSHAL", setter = true)
+ public void setSchemaMarshal(Text level);
}
=== modified file 'src/com/goldencode/p2j/persist/TempTableBuilder.java'
--- src/com/goldencode/p2j/persist/TempTableBuilder.java 2020-10-02 22:45:29 +0000
+++ src/com/goldencode/p2j/persist/TempTableBuilder.java 2021-01-21 22:49:45 +0000
@@ -3,7 +3,7 @@
** Abstract : Implementation of the dynamic features for constructing temp-tables
* (attributes, methods and constructors).
**
-** Copyright (c) 2013-2020, Golden Code Development Corporation.
+** Copyright (c) 2013-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM 20130129 Created initial version.
@@ -82,6 +82,14 @@
** OM 20200924 Objects of this class carry multiple information to avoid map lookups for them.
** OM 20201001 Improved DMO manipulation performance by caching slow Property annotation access.
** OM 20201002 Use DmoMeta cached information instead of map lookups.
+** SVL 20201030 Reflect extension of PropertyDefinition.
+** SVL 20201110 Manage BufferField handles in addFieldLike.
+** OM 20201120 Renamed NAMESPACE-URI and NAMESPACE-PREFIX setters to fit buffer attributes naming
+** convention. Allowed Object fields to be added to TEMP-TABLEs. Added CLEAR state
+** awareness. Exposed addField() to XML deserializer.
+** OM 20201202 Avoided conditions to be generated while encountered exeptions in debug mode.
+** 20210106 Dropped 2nd parameter of TEMP-TABLE-PREPARE.
+** OM 20210115 The before-table is automaticaly created when needed.
*/
/*
@@ -233,8 +241,7 @@
ParmType.CHAR})
{
String typeName = type.toString();
- defaultFormatStrings.put(typeName,
- DisplayFormat.instanceOfType(typeName).defaultFormatString());
+ defaultFormatStrings.put(typeName, DisplayFormat.instanceOfType(typeName).defaultFormatString());
}
// separately because used "datetimetz" and "datetime-tz"
defaultFormatStrings.put(ParmType.DTTZ.toString(),
@@ -256,8 +263,6 @@
*
* @param tableName
* The table name.
- * @param afterTable
- * Flag indentifying if this is an after or before table.
* @param props
* The property definitions.
* @param tableIndexes
@@ -268,7 +273,6 @@
* The XML prefix.
*/
public static BufferImpl createRemoteTable(String tableName,
- boolean afterTable,
Iterator props,
String tableIndexes,
String xmlns,
@@ -304,20 +308,20 @@
}
}
- ttb.tempTablePrepare(tableName, afterTable);
+ ttb.tempTablePrepare(tableName);
BufferImpl buff = (BufferImpl) ttb.defaultBufferHandle().getResource();
if (xmlns != null)
{
- buff.setNamespaceURI(xmlns);
+ buff.namespaceURI(xmlns);
}
if (xmlPrefix != null)
{
- buff.setNamespacePrefix(xmlPrefix);
+ buff.namespacePrefix(xmlPrefix);
}
return buff;
}
-
+
/**
* Dynamically creates a temporary table at runtime and assign it to a
* handle. The table is empty and will be populated with fields, indexes
@@ -446,7 +450,7 @@
}
/**
- * Get the field of an existing table.
+ * Get the field (legacy name) of an existing table.
*
* @param persistence
* Persistence which corresponds the target database.
@@ -455,22 +459,21 @@
* @param fieldName4GL
* 4GL field name.
*
- * @return field with the specified name or null if there is no such field.
+ * @return field with the specified name or {@code null} if there is no such field.
*/
public static P2JField getExistingField(Persistence persistence, String tableName4GL, String fieldName4GL)
{
String normalizedTableName4GL = normalizeName(tableName4GL);
String normalizedFieldName4GL = normalizeName(fieldName4GL);
- TempTable tt = null;
String propertyName;
- Class extends Record> dmoClass;
+ DmoMeta dmoInfo;
Database database = persistence.getDatabase();
if (DatabaseManager.TEMP_TABLE_DB.equals(database))
{
- tt = getExistingTempTable(normalizedTableName4GL);
+ TempTable tt = getExistingTempTable(normalizedTableName4GL);
propertyName = TableMapper.getPropertyName(tt, normalizedFieldName4GL);
- dmoClass = tt.getDMOClass();
+ dmoInfo = tt.getDmoMeta();
}
else
{
@@ -478,7 +481,7 @@
propertyName = TableMapper.getPropertyName(schema,
normalizedTableName4GL,
normalizedFieldName4GL);
- dmoClass = TableMapper.getDMOClass(schema, normalizedTableName4GL);
+ dmoInfo = DmoMetadataManager.getDmoInfo(TableMapper.getDMOClass(schema, normalizedTableName4GL));
}
if (propertyName == null)
@@ -486,51 +489,49 @@
return null;
}
- return getExistingField(DmoMetadataManager.getDMOInterface(dmoClass), propertyName, tt);
+ return getExistingField(dmoInfo, propertyName);
}
-
+
/**
* Get the field of an existing table.
*
* @param buffer
* A record buffer which backs records of the target table.
- * @param fieldNameHibernate
- * Hibernate field name.
+ * @param propertyName
+ * The property name.
*
- * @return field with the specified name or null if there is no such field.
+ * @return field with the specified name or {@code null} if there is no such field.
*/
- public static P2JField getExistingField(RecordBuffer buffer, String fieldNameHibernate)
+ public static P2JField getExistingField(RecordBuffer buffer, String propertyName)
{
if (buffer.isDynamic())
{
TempTableBuilder table = DynamicTablesHelper.getTableInfo(buffer);
Class extends DataModelObject> dmoIface = buffer.getDMOInterface();
- String fieldName4GL = DynamicTablesHelper.get4GLFieldName(dmoIface, fieldNameHibernate);
+ String fieldName4GL = DynamicTablesHelper.get4GLFieldName(dmoIface, propertyName);
return table.getField(fieldName4GL);
}
else
{
TempTable tt = buffer.isTemporary() ? buffer.getParentTable() : null;
- return getExistingField(buffer.getDMOInterface(), fieldNameHibernate, tt);
+ return getExistingField(tt.getDmoMeta(), propertyName);
}
}
/**
- * Get the field of an existing table.
+ * Get the field (legacy name) of an existing table.
*
- * @param dmoIface
- * DMO interface.
+ * @param dmoInfo
+ * DMO metadata for the table.
* @param propertyName
* Property name of the field.
- * @param tempTable
- * Temp table object. {@code null} for permanent tables.
*
* @return field with the specified name or {@code null} if there is no such field.
*/
- public static P2JField getExistingField(Class extends DataModelObject> dmoIface,
- String propertyName,
- TempTable tempTable)
+ public static P2JField getExistingField(DmoMeta dmoInfo, String propertyName)
{
+ Class extends DataModelObject> dmoIface = dmoInfo.getAnnotatedInterface();
+
if (!PropertyHelper.isLoaded(dmoIface))
{
// just create an instance and analyze it;
@@ -539,7 +540,6 @@
Map getters = PropertyHelper.legacyGettersByProperty(dmoIface);
Map extents = PropertyHelper.extentsByProperty(dmoIface);
- DmoMeta dmoInfo = DmoMetadataManager.getDmoInfo(dmoIface);
// get legacy property name
Property fieldInfo = dmoInfo.getFieldInfo(propertyName);
@@ -815,30 +815,30 @@
public static Collection getExistingFields(Persistence persistence, String tableName4GL)
{
String normalizedTableName4GL = normalizeName(tableName4GL);
-
Database database = persistence.getDatabase();
- Class extends Record> dmoClass;
StaticTempTable staticTempTable = null;
+ DmoMeta dmoInfo = null;
if (database.equals(DatabaseManager.TEMP_TABLE_DB))
{
staticTempTable = (StaticTempTable) getExistingTempTable(normalizedTableName4GL);
- dmoClass = staticTempTable.getDMOClass();
+ dmoInfo = staticTempTable.getDmoMeta();
}
else
{
String schema = DatabaseManager.getSchema(database);
- dmoClass = TableMapper.getDMOClass(schema, normalizedTableName4GL);
+ dmoInfo = DmoMetadataManager.getDmoInfo(TableMapper.getDMOClass(schema, normalizedTableName4GL));
}
- Class extends DataModelObject> dmoIface = DmoMetadataManager.getDMOInterface(dmoClass);
+ // TODO: use [dmoInfo.getExistingFields()] instead, after adding field/property/column support in P2JField
+ Class extends DataModelObject> dmoIface = dmoInfo.getAnnotatedInterface();
String[] properties = getPropertyNamesArray(dmoIface, staticTempTable);
List res = new ArrayList<>();
for (String property : properties)
{
// TODO: the code below collects "legacy" fields only. Is this correct?
- P2JField field = getExistingField(dmoIface, property, staticTempTable);
+ P2JField field = getExistingField(dmoInfo, property);
res.add(field);
}
@@ -1048,11 +1048,15 @@
// get first word
int spaceIdx = datatype.indexOf(' ');
String compareStr = spaceIdx >= 0 ? datatype.substring(0, spaceIdx) : datatype;
-
+
if (compareStr.length() > 0)
{
compareStr = compareStr.toLowerCase();
-
+ if ("progress.lang.object".equals(compareStr))
+ {
+ compareStr = "object";
+ }
+
for (String type : datatypesMatchLen.keySet())
{
if (type.startsWith(compareStr))
@@ -1700,9 +1704,25 @@
// we have the field with the same name
return new logical(false);
}
-
+
+ RecordBuffer buffer;
+ String fieldNameHibernate;
Object handleValue = source.get();
- if (!FieldReference.class.isAssignableFrom(handleValue.getClass()))
+ Class> cls = handleValue.getClass();
+ if (FieldReference.class.isAssignableFrom(cls))
+ {
+ FieldReference ref = (FieldReference) handleValue;
+ buffer = ref.getParentBuffer();
+ fieldNameHibernate = ref.getProperty();
+ }
+ else if (BufferField.class.isAssignableFrom(cls))
+ {
+ BufferFieldImpl field = (BufferFieldImpl) handleValue;
+ Buffer buf = field.bufferHandle().unwrapBuffer();
+ buffer = RecordBuffer.get((DataModelObject) buf);
+ fieldNameHibernate = field.getProperty();
+ }
+ else
{
ErrorManager.displayError(9056,
"ADD-LIKE-FIELD requires valid BUFFER-FIELD object",
@@ -1710,10 +1730,6 @@
return new logical(false);
}
- FieldReference ref = (FieldReference) handleValue;
- RecordBuffer buffer = ref.getParentBuffer();
- String fieldNameHibernate = ref.getProperty();
-
P2JField srcField = getExistingField(buffer, fieldNameHibernate);
P2JField targetField = copyField(targetName, srcField);
@@ -2184,6 +2200,22 @@
}
/**
+ * Check whether this TEMP-TABLE is in CLEAR state: I.e. the temp-table is first created or immediately
+ * after the CLEAR() method is applied. The other two state of a temp-table are: UNPREPARED and PREPARED.
+ * The UNPREPARED state between the first definitional method has been applied and before the
+ * TEMP-TABLE-PREPARE() method is applied.
+ *
+ * This method is FWD-internal. The programmer can check whether the temp-table is in an UNPREPARED or
+ * PREPARED state by checking the PREPARED attribute, but the CLEAR state is transparent.
+ *
+ * @return {@code true} if this table is in CLEAR state.
+ */
+ public boolean _clear()
+ {
+ return fields.size() == 0;
+ }
+
+ /**
* Checks whether the resource is dynamic or static.
*
* @return true if the resource is dynamic, false otherwise.
@@ -2221,50 +2253,6 @@
}
/**
- * Signals that all the field and index definitions for a temp-table have been provided. After
- * the call to this method no fields and indexes can be added to this temporary table.
- * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
- *
- * @param name
- * Temp-table name to be used in subsequent query statements which refer to this
- * temp-table.
- * @param before
- * Create the {@code BEFORE-TABLE} also if {@code true}.
- *
- * @return {@code true} on success.
- */
- @Override
- public logical tempTablePrepare(String name, boolean before)
- {
- return tempTablePrepare(new character(name), new logical(before));
- }
-
- /**
- * Signals that all the field and index definitions for a temp-table have been provided. After
- * the call to this method no fields and indexes can be added to this temporary table.
- * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
- *
- * @param name
- * Temp-table name to be used in subsequent query statements which refer to this
- * temp-table.
- * @param before
- * Create the {@code BEFORE-TABLE} also if {@code true}.
- *
- * @return {@code true} on success.
- */
- @Override
- public logical tempTablePrepare(character name, logical before)
- {
- String tableName = name.getValue();
- String beforeName = null;
- if (before != null && !before.isUnknown() && before.getValue())
- {
- beforeName = "BI" + tableName;
- }
- return tempTablePrepareImpl(tableName, beforeName);
- }
-
- /**
* The implementation of {@code TEMP-TABLE-PREPARE} method. This method is invoked from both
* user API and also from {@code DataSet} when a DATASET is duplicated.
*
@@ -2377,36 +2365,7 @@
if (beforeName != null)
{
- // create the BEFORE-TABLE
- handle btth = TypeFactory.handle();
- TempTableBuilder.create(btth);
- BufferImpl srcB4Buff = (BufferImpl) defaultBufferHandle().getResource();
-
- TempTableBuilder newBtt = (TempTableBuilder) btth.getResource();
- newBtt.addField(buildField(ReservedProperty._ERRORFLAG, ParmType.INT, new integer()));
- newBtt.addField(buildField(ReservedProperty._ORIGINROWID, ParmType.ROWID, new rowid()));
- newBtt.addField(buildField(ReservedProperty._ERRORSTRING, ParmType.CHAR, new character()));
- newBtt.addField(buildField(ReservedProperty._PEERROWID, ParmType.ROWID, new rowid()));
- newBtt.addField(buildField(ReservedProperty._ROWSTATE, ParmType.INT, new integer()));
-
- newBtt.createLike(new handle(srcB4Buff));
-
- // leave only the rowState index
- newBtt.indexes.clear();
- P2JIndex rowStateIndex = new P2JIndex(beforeName, "rowState", false);
- rowStateIndex.addComponent(ReservedProperty._ROWSTATE.legacy);
- newBtt.addIndex(rowStateIndex);
-
- res = newBtt.tempTablePrepareImpl(beforeName, null).booleanValue();
-
- if (res)
- {
- // dynamically link the new before TempTable to this after TempTable
- newBtt.beforeTableType = BeforeType.BEFORE;
- newBtt.peerTable = this;
- this.beforeTableType = BeforeType.AFTER;
- this.peerTable = newBtt;
- }
+ res = createBeforeTable(beforeName);
}
if (beforeTableType == BeforeType.UNKNOWN)
@@ -2421,41 +2380,159 @@
return new logical(res);
}
-
- /**
- * Signals that all the field and index definitions for a temp-table
- * have been provided. After the call to this method no fields and indexes
- * can be added to this temporary table.
- * This method is the P2J equivalent of TEMP-TABLE-PREPARE
- * method of Progress 4GL.
+
+ /**
+ * Creates the before table and link it to current table.
+ *
+ * @param beforeName
+ * The name of the before table to be build.
+ *
+ * @return {@code true} if operation is successful and {@code false} otherwise.
+ */
+ boolean createBeforeTable(String beforeName)
+ {
+ boolean res;
+ // create the BEFORE-TABLE
+ handle btth = TypeFactory.handle();
+ TempTableBuilder.create(btth);
+ BufferImpl srcB4Buff = (BufferImpl) defaultBufferHandle().getResource();
+
+ TempTableBuilder newBtt = (TempTableBuilder) btth.getResource();
+ newBtt.addField(buildField(ReservedProperty._ERRORFLAG, ParmType.INT, new integer()));
+ newBtt.addField(buildField(ReservedProperty._ORIGINROWID, ParmType.ROWID, new rowid()));
+ newBtt.addField(buildField(ReservedProperty._ERRORSTRING, ParmType.CHAR, new character()));
+ newBtt.addField(buildField(ReservedProperty._PEERROWID, ParmType.ROWID, new rowid()));
+ newBtt.addField(buildField(ReservedProperty._ROWSTATE, ParmType.INT, new integer()));
+
+ newBtt.createLike(new handle(srcB4Buff));
+
+ // leave only the rowState index
+ newBtt.indexes.clear();
+ P2JIndex rowStateIndex = new P2JIndex(beforeName, "rowState", false);
+ rowStateIndex.addComponent(ReservedProperty._ROWSTATE.legacy);
+ newBtt.addIndex(rowStateIndex);
+
+ res = newBtt.tempTablePrepareImpl(beforeName, null).booleanValue();
+
+ if (res)
+ {
+ // dynamically link the new before TempTable to this after TempTable
+ newBtt.beforeTableType = BeforeType.BEFORE;
+ newBtt.peerTable = this;
+ this.beforeTableType = BeforeType.AFTER;
+ this.peerTable = newBtt;
+ // then update the buffer types and link them using peerBuffer
+ srcB4Buff.computeBufferType();
+ srcB4Buff.peerBuffer.computeBufferType();
+ }
+ return res;
+ }
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After the call to
+ * this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
*
* @param name
- * Temp-table name to be used in subsequent query statements that
- * refer to this temp-table.
+ * Temp-table name to be used in subsequent query statements that refer to this temp-table.
*
- * @return true on success.
+ * @return {@code true} on success.
*/
public logical tempTablePrepare(String name)
{
- return tempTablePrepare(new character(name), new logical(false));
+ return tempTablePrepareImpl(name, null);
}
/**
- * Signals that all the field and index definitions for a temp-table
- * have been provided. After the call to this method no fields and indexes
- * can be added to this temporary table.
- * This method is the P2J equivalent of TEMP-TABLE-PREPARE
- * method of Progress 4GL.
- *
+ * Signals that all the field and index definitions for a temp-table have been provided. After the call to
+ * this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
* @param name
- * Temp-table name to be used in subsequent query statements that
- * refer to this temp-table.
- *
- * @return true on success.
+ * Temp-table name to be used in subsequent query statements that refer to this temp-table.
+ *
+ * @return {@code true} on success.
*/
public logical tempTablePrepare(character name)
{
- return tempTablePrepare(name, new logical(false));
+ String tableName = name.toJavaType();
+ return tempTablePrepareImpl(tableName, null);
+ }
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After
+ * the call to this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
+ * @param name
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
+ * @param before
+ * Create the {@code BEFORE-TABLE} also if {@code true}.
+ *
+ * @return {@code true} on success.
+ */
+ @Override
+ public logical tempTablePrepare(String name, boolean before)
+ {
+ return tempTablePrepareImpl(name, (before && name != null) ? "BI" + name : null);
+ }
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After
+ * the call to this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
+ * @param name
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
+ * @param before
+ * Create the {@code BEFORE-TABLE} also if {@code true}.
+ *
+ * @return {@code true} on success.
+ */
+ @Override
+ public logical tempTablePrepare(String name, logical before)
+ {
+ boolean boolBef = !before.isUnknown() && before.booleanValue();
+ return tempTablePrepareImpl(name, (boolBef && name != null) ? "BI" + name : null);
+ }
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After
+ * the call to this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
+ * @param name
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
+ * @param before
+ * Create the {@code BEFORE-TABLE} also if {@code true}.
+ *
+ * @return {@code true} on success.
+ */
+ @Override
+ public logical tempTablePrepare(character name, boolean before)
+ {
+ String tableName = name.toJavaType();
+ return tempTablePrepareImpl(tableName, (before && tableName != null) ? "BI" + tableName : null);
+ }
+
+ /**
+ * Signals that all the field and index definitions for a temp-table have been provided. After
+ * the call to this method no fields and indexes can be added to this temporary table.
+ * This method is the P2J equivalent of {@code TEMP-TABLE-PREPARE} method of Progress 4GL.
+ *
+ * @param name
+ * Temp-table name to be used in subsequent query statements which refer to this temp-table.
+ * @param before
+ * Create the {@code BEFORE-TABLE} also if {@code true}.
+ *
+ * @return {@code true} on success.
+ */
+ @Override
+ public logical tempTablePrepare(character name, logical before)
+ {
+ boolean boolBef = !before.isUnknown() && before.booleanValue();
+ String tableName = name.toJavaType();
+ return tempTablePrepareImpl(tableName, (boolBef && tableName != null) ? "BI" + tableName : null);
}
/**
@@ -2897,7 +2974,7 @@
@Override
public Class extends Record> getDMOClass()
{
- return DmoMetadataManager.getImplementingClass(dmoIface);
+ return dmoInfo.getImplementationClass();
}
/**
@@ -2997,18 +3074,15 @@
// ParmType has this encoded as FWD class names, I'm not changing it there as it might affect conversion
String dataType = parmType == ParmType.DTTZ ? "datetime-tz" : parmType.toString();
-
- if (propertyDefinition.isExtent())
- {
- addNewField(new character(propertyDefinition.getLegacyName()),
- new character(dataType),
- new integer(propertyDefinition.getExtent()));
- }
- else
- {
- addNewField(new character(propertyDefinition.getLegacyName()),
- new character(dataType));
- }
+ integer extent = propertyDefinition.isExtent() ? new integer(propertyDefinition.getExtent()) : null;
+
+ addNewField(new character(propertyDefinition.getLegacyName()),
+ new character(dataType),
+ extent,
+ new character(propertyDefinition.getFormat()),
+ null,
+ new character(propertyDefinition.getLabel()),
+ new character(propertyDefinition.getColumnLabel()));
}
/**
@@ -3535,13 +3609,16 @@
}
/**
- * Add new field to the table. Doesn't check whether the field with the same name already
- * exists.
+ * Add new field to the table. Doesn't check whether the field with the same name already exists.
+ *
+ * Note:
+ * This method was made public in order to be directly accessed from
+ * {@link com.goldencode.p2j.persist.serial.XmlImport}.
*
* @param field
* Field to add.
*/
- private void addField(P2JField field)
+ public void addField(P2JField field)
{
fields.put(normalizeName(field.getName()), field);
}
@@ -3635,7 +3712,28 @@
String myName = (name == null) ? "unnamed" : name().getValue();
longchar lc = new longchar();
- writeJson(new TargetData(lc), new logical(true));
+ ErrorManager.ErrorHelper eh = ErrorManager.getErrorHelper();
+ boolean ws = eh.isSilent();
+ boolean wp = eh.isPending();
+ if (!ws)
+ {
+ eh.setSilent(true);
+ }
+ try
+ {
+ writeJson(new TargetData(TargetData.TYPE_LONGCHAR, lc), new logical(true));
+ }
+ finally
+ {
+ if (!ws)
+ {
+ eh.setSilent(false);
+ }
+ if (!wp && eh.isPending())
+ {
+ eh.clearPending();
+ }
+ }
return "DYNAMIC " + myName + ", ID " + getUniqueID().getValue() + "\n" + lc.toStringMessage();
}
}
=== modified file 'src/com/goldencode/p2j/persist/TempTableRecord.java'
--- src/com/goldencode/p2j/persist/TempTableRecord.java 2020-09-06 23:15:41 +0000
+++ src/com/goldencode/p2j/persist/TempTableRecord.java 2021-01-13 21:04:41 +0000
@@ -4,12 +4,13 @@
**
** Copyright (c) 2019-2020, Golden Code Development Corporation.
**
-** -#- -I- --Date-- ---------------------------------Description---------------------------------
+** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM 20190604 Initial version.
** 002 OM 20190831 Added hidden fields and index specific to BEFORE TEMP-TABLES.
** 003 OM 20191014 Before-Buffer specific column constants were moved to Buffer interface.
** 004 CA 20191119 Added toArray(), which returns the meta information in an array form.
** 005 ECF 20200906 New ORM implementation.
+** OM 20201218 Fixed implementation for error and rejected attributes/hidden fields.
*/
/*
@@ -158,7 +159,7 @@
*
* @see Buffer#__ERROR_FLAG__
*/
- public Integer _errorFlag();
+ public Integer _errorFlags();
/**
* Sets the current value of the {@code __error-flag__} for this record.
@@ -168,7 +169,7 @@
*
* @see Buffer#__ERROR_FLAG__
*/
- public void _errorFlag(Integer flag);
+ public void _errorFlags(Integer flag);
/**
* Gets the current value of the {@code __error-string__} for this record.
@@ -202,7 +203,7 @@
this._rowState(),
this._peerRowid(),
this._originRowid(),
- this._errorFlag(),
+ this._errorFlags(),
this._errorString()
};
}
=== modified file 'src/com/goldencode/p2j/persist/TempTableResultSet.java'
--- src/com/goldencode/p2j/persist/TempTableResultSet.java 2020-10-01 22:14:40 +0000
+++ src/com/goldencode/p2j/persist/TempTableResultSet.java 2020-11-20 18:40:59 +0000
@@ -26,6 +26,7 @@
** 014 CA 20200427 Flush the buffer before reading the records.
** 015 ECF 20200906 New ORM implementation.
** 016 OM 20201001 Improved DMO manipulation performance by caching slow Property annotation access.
+** SVL 20201030 Reflect extension of PropertyDefinition.
*/
/*
@@ -356,12 +357,23 @@
if (extent == 0)
{
props[dmoPropIndex.get(prop.name)] =
- new PropertyDefinition(prop.name, prop._fwdType, prop.legacy);
+ new PropertyDefinition(prop.name,
+ prop._fwdType,
+ prop.legacy,
+ prop.format,
+ prop.label,
+ prop.columnLabel);
}
else
{
props[dmoPropIndex.get(prop.name)] =
- new PropertyDefinition(prop.name, prop._fwdType, extent, prop.legacy);
+ new PropertyDefinition(prop.name,
+ prop._fwdType,
+ extent,
+ prop.legacy,
+ prop.format,
+ prop.label,
+ prop.columnLabel);
}
}
=== modified file 'src/com/goldencode/p2j/persist/TemporaryBuffer.java'
--- src/com/goldencode/p2j/persist/TemporaryBuffer.java 2020-10-05 18:05:51 +0000
+++ src/com/goldencode/p2j/persist/TemporaryBuffer.java 2021-01-29 00:53:41 +0000
@@ -2,7 +2,7 @@
** Module : TemporaryBuffer.java
** Abstract : Specialized record buffer which supports temp table semantics
**
-** Copyright (c) 2004-2020, Golden Code Development Corporation.
+** Copyright (c) 2004-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- -----------------------------------Description-----------------------------------
** 001 ECF 20060125 @24356 Created initial version. Extends RecordBuffer
@@ -441,8 +441,7 @@
** DMO alias binding is limited only for the duration of that top-level
** block call.
** 152 IAS 20200413 Fixed DMO instantiation on SESSION:DATE-FORMAT.
-** CA 20200427 Allow a TABLE to be read from JSON argument, in case of a remote REST
-** call.
+** CA 20200427 Allow a TABLE to be read from JSON argument, in case of a remote REST call.
** CA 20200514 Added support for SOAP web services (field are sent as BDT already, but the
** 'fromJson' flags remains).
** 153 ECF 20200419 Removed Hibernate dependencies.
@@ -468,6 +467,20 @@
** Use an identity HashSet where possible.
** CA 20201005 Fixed bulkCopyAllRows and copyAllRows - H2 will now match the WHERE and ORDER BY
** indexes, avoiding re-sorting the fetched rows.
+** AIL 20201031 Added sorted and direct keywords for insert into select from.
+** AIL 20201118 Made extent fields bulk copy use insert into select from.
+** 20201125 Added fast copy validation using DMO signature.
+** 20201201 Moved fast copy implementation to FastCopyHelper. Added helper methods.
+** CA 20201202 Record change notifications are processed only if the multiplex ID matches with
+** the event's source.
+** AIL 20201215 Added before table prop mapping to the fast-copy helper call.
+** 20201216 Added support for fast loose-copy.
+** 20201228 Invalidated fast-find cache after fast-copy.
+** CA 20201118 Fixed a leak related to cleaning up the multiplex scope when deleting a static
+** temp-table part of a persistent external program.
+** OM 20201120 Fixed buffer copy methods. Fixed rejectChanges() method. Fixed management of
+** BEFORE-BUFFER atttibutes. Fixed buffer/table copy.
+** OM 20201218 Fixed implementation for error and rejected attributes/hidden fields.
*/
/*
@@ -691,11 +704,11 @@
/** The actual proxy instance wrapping the DMO proxy. */
private Temporary mutableProxy;
- /**
+ /**
* For a master buffer, all explicit buffers created for it.
* TODO: I think there is a memory leak hear, needs to be reviewed.
*/
- private Set explicitBuffers = Collections.newSetFromMap(new IdentityHashMap<>());
+ private final Set explicitBuffers = Collections.newSetFromMap(new IdentityHashMap<>());
/**
* Constructor. This class is only instantiated via the {@link #define}
@@ -2757,7 +2770,7 @@
dstRec._rowState((Integer) rowMeta[1]);
dstRec._peerRowid((Long) rowMeta[2]); // this will be processed later on
// TODO: originRowId - does it have any meaning?
- dstRec._errorFlag((Integer) rowMeta[4]);
+ dstRec._errorFlags((Integer) rowMeta[4]);
dstRec._errorString((String) rowMeta[5]);
savedAny = true;
@@ -3157,48 +3170,49 @@
}
/**
- * Copy all records from one temp table to another, optionally appending to records already
- * present in the destination table.
+ * Copy all records from one temp table to another, optionally appending to records already present in the
+ * destination table.
*
* @param srcRecId
- * The {@link rowid} of the current record in the source buffer - if not null, it will
- * be set in the destination buffer, too.
+ * The {@link rowid} of the current record in the source buffer - if not null, it will be set in
+ * the destination buffer, too.
* @param srcDMO
- * DMO instance returned by a previous call to {@link #define} on the source
- * temporary buffer.
+ * DMO instance returned by a previous call to {@link #define} on the source temporary buffer.
* @param dstDMO
- * DMO instance returned by a previous call to {@link #define} on the
- * destination temporary buffer.
+ * DMO instance returned by a previous call to {@link #define} on the destination temporary
+ * buffer.
* @param append
- * true to add new records without affecting existing records;
- * false to remove all existing records before the copy takes place.
+ * {@code true} to add new records without affecting existing records;
+ * {@code false} to remove all existing records before the copy takes place.
* @param errorMode
* Table copy mode, affects error handling.
* @param skipUniqueConflicts
- * Corresponds append-mode in COPY-TEMP-TABLE. If there is a unique index on the
- * target temp-table and a row with a duplicate key is found, the row is not replaced.
- * This parameter is ignored if replacePrimaryRecords is
- * true.
- * @param replacePrimaryRecords
- * TODO implement support
- * Corresponds replace-mode in COPY-TEMP-TABLE. When true records in the
- * target temp-table are replaced with corresponding records from the source
- * temp-table. The target temp-table must have a unique primary index that be can used
- * to find the corresponding record. When the corresponding record is found in the
- * target temp-table, the target record is replaced with the source record.
+ * Corresponds append-mode in COPY-TEMP-TABLE. If there is a unique index on the target temp-table
+ * and a row with a duplicate key is found, the row is not replaced. This parameter is ignored if
+ * {@code replaceMode} is {@code true}.
+ * @param replaceMode
+ * Corresponds replace-mode in COPY-TEMP-TABLE. When {@code true} records in the target temp-table
+ * are replaced with corresponding records from the source temp-table. The target temp-table must
+ * have a unique primary index that be can used to find the corresponding record. When the
+ * corresponding record is found in the target temp-table, the target record is replaced with the
+ * source record.
* @param looseCopy
- * Corresponds loose-copy-mode in COPY-TEMP-TABLE. Only the fields with the same name
- * that appear in both the source and target temp-table are copied. Other fields are
- * ignored.
+ * Corresponds loose-copy-mode in COPY-TEMP-TABLE. Only the fields with the same name that appear
+ * in both the source and target temp-table are copied. Other fields are ignored.
+ *
Note:
+ * Because of an undocumented bug in OE, if the {@code replaceMode} is {@code true} the value will
+ * 'leak' and overwrite this parameter. As result, a temp-table can be copied to another,
+ * incompatible temp-table even if {@code looseCopy} is explicitly set to {@code false}, just by
+ * passing {@code true} for the previous parameter.
*
- * @return true if copy was successful. false if validation error
- * has occurred or the table is appended to itself.
+ * @return {@code true} if copy was successful.
+ * {@code false} if validation error has occurred or the table is appended to itself.
*
* @throws ErrorConditionException
* if a recoverable error occurs copying records.
* @throws RuntimeException
- * if an unrecoverable error occurs invoking methods on the underlying DMOs to get or
- * set data values.
+ * if an unrecoverable error occurs invoking methods on the underlying DMOs to get or set data
+ * values.
*/
static boolean copyAllRows(rowid srcRecId,
Temporary srcDMO,
@@ -3206,10 +3220,16 @@
boolean append,
CopyTableMode errorMode,
boolean skipUniqueConflicts,
- boolean replacePrimaryRecords,
+ boolean replaceMode,
boolean looseCopy)
{
- // Get source and destination buffers, both of which must be TemporaryBuffer instances.
+ if (replaceMode)
+ {
+ // see the javadoc note above for [looseCopy] parameter
+ looseCopy = true;
+ }
+
+ // get source and destination buffers, both of which must be TemporaryBuffer instances
TemporaryBuffer srcBuf = (TemporaryBuffer) ((BufferReference) srcDMO).buffer();
TemporaryBuffer dstBuf = (TemporaryBuffer) ((BufferReference) dstDMO).buffer();
@@ -3222,10 +3242,12 @@
TemporaryBuffer dstB4Buf = null;
boolean before = false;
- if (((BufferImpl) srcDMO).isAfterBuffer() && ((BufferImpl) dstDMO).isAfterBuffer())
+ BufferImpl dstBufImpl = (BufferImpl) dstDMO;
+ BufferImpl srcBufImpl = (BufferImpl) srcDMO;
+ if (srcBufImpl.isAfterBuffer() && dstBufImpl.isAfterBuffer())
{
- srcB4DMO = (BufferImpl) ((BufferImpl) srcDMO).beforeBuffer().getResource();
- dstB4DMO = (BufferImpl) ((BufferImpl) dstDMO).beforeBuffer().getResource();
+ srcB4DMO = (BufferImpl) srcBufImpl.beforeBuffer().getResource();
+ dstB4DMO = (BufferImpl) dstBufImpl.beforeBuffer().getResource();
srcB4Buf = (TemporaryBuffer) ((BufferReference) srcB4DMO).buffer();
dstB4Buf = (TemporaryBuffer) ((BufferReference) dstB4DMO).buffer();
@@ -3279,8 +3301,8 @@
{
if (errorMode == CopyTableMode.COPY_TEMP_TABLE_MODE)
{
- String msg = "Source and target are the same table in temp-table copy " +
- "operation for " + dstBufName;
+ String msg = "Source and target are the same table in temp-table copy operation for " +
+ dstBufName;
ErrorManager.displayError(12301, msg, false);
return false;
@@ -3299,12 +3321,10 @@
boolean simpleCopy = srcBuf.getDMOImplementationClass().equals(dstBuf.getDMOImplementationClass());
- // PROGRESS does not care if the field names are different; what it
- // cares about are only the field type and position - if a source field
- // has the same position and type as the destination field, then they
- // are a match. If the source and destination fields at a certain
- // position do not match, then PROGRESS will throw an error and abend
- // the application.
+ // PROGRESS does not care if the field names are different; what it cares about are only the field type
+ // and position - if a source field has the same position and type as the destination field, then they
+ // are a match. If the source and destination fields at a certain position do not match, then PROGRESS
+ // will throw an error and abend the application.
// if the backing tables are different, check if the fields match
Map propsMap = null;
@@ -3313,7 +3333,7 @@
if (!simpleCopy)
{
// we need to modify the setter properties, so instantiate a new map
- propsMap = createPropsMap(looseCopy, (BufferImpl) srcDMO, (BufferImpl) dstDMO, errorMode);
+ propsMap = createPropsMap(looseCopy, srcBufImpl, dstBufImpl, errorMode);
if (propsMap == null)
{
return false;
@@ -3366,122 +3386,79 @@
if (!srcBuf.isTableDefinitelyEmpty())
{
- DmoMeta dstMeta = dstBuf.getDmoInfo();
-
- // skip validation when:
- // 1. same table and not in append mode (as dst will be empty) or
- // 2. there are no unique constraints or mandatory fields in dst table
- // TODO: enhance this if we are not in append mode, the dst and src tables are different, and they
- // have the same set of unique indexes (by the field position, not name) and the same set of
- // mandatory fields (by field position, not name)
- boolean bulkInsert = !append &&
- ((dstBuf.getDMOImplementationClass() == srcBuf.getDMOImplementationClass()) ||
- (dstMeta.getUniqueConstraints().isEmpty() && dstMeta.getMandatoryFields().isEmpty()));
-
- if (bulkInsert)
- {
- boolean hasBeforeRecords = before && !srcB4Buf.isTableDefinitelyEmpty();
- String srcSqlIndexName = srcBuf.local.getImplicitSqlIndexName(srcBuf.getDMOInterface());
- String srcIndexName = srcBuf.local.getImplicitIndexName(srcBuf.getDMOInterface());
-
- Map srcToDstPks = bulkCopyAllRows(simpleCopy,
- srcBuf,
- dstBuf,
- hasBeforeRecords || srcRecId != null,
- propsMap,
- srcSqlIndexName,
- srcIndexName);
- if (hasBeforeRecords)
- {
- String srcB4SqlIndexName = srcB4Buf.local.getImplicitSqlIndexName(srcB4Buf.getDMOInterface());
- String srcB4IndexName = srcB4Buf.local.getImplicitIndexName(srcB4Buf.getDMOInterface());
- Map srcB4ToDstB4Pks = bulkCopyAllRows(simpleCopy,
- srcB4Buf,
- dstB4Buf,
- true,
- propsMap,
- srcB4SqlIndexName,
- srcB4IndexName);
-
- DmoMeta dstB4Meta = dstB4Buf.getDmoInfo();
- String dstTableB4 = dstB4Meta.getSqlTableName();
-
- List sqls = new ArrayList<>();
- for (Map.Entry entries : srcToDstPks.entrySet())
- {
- Long srcPk = entries.getKey();
- Long[] pair = entries.getValue();
- if (pair[1] == null)
- {
- continue;
- }
-
- Long dstPk = pair[0];
- Long srcPeerId = pair[1];
- Long dstB4Pk = srcB4ToDstB4Pks.get(srcPeerId)[0];
-
- sqls.add("update " + dstTableB4 +
- " set " + TempRecord._PEER_ROWID + " = " + dstPk +
- " where " + Session.PK + " = " + dstB4Pk);
- }
-
- String dstTable = dstMeta.getSqlTableName();
- for (Map.Entry entries : srcB4ToDstB4Pks.entrySet())
- {
- Long srcB4Pk = entries.getKey();
- Long[] pair = entries.getValue();
- if (pair[1] == null)
- {
- continue;
- }
-
- Long dstB4Pk = pair[0];
- Long srcB4PeerId = pair[1];
- Long dstPk = srcToDstPks.get(srcB4PeerId)[0];
-
- sqls.add("update " + dstTable +
- " set " + TempRecord._PEER_ROWID + " = " + dstB4Pk +
- " where " + Session.PK + " = " + dstPk);
- }
-
- persistence.executeSQLBatch(sqls, true);
- }
-
- // at this point, we are sure the source table is not empty and all records were copied.
- savedAny = true;
-
- if (srcRecId != null)
- {
- destRecId = new rowid(srcToDstPks.get(srcRecId.getValue())[0]);
- }
-
- return true;
+ String replaceQuery = null;
+ if (replaceMode)
+ {
+ P2JIndex primaryIndex = dstBuf.dmoInfo.getPrimaryIndex(true);
+ if (primaryIndex == null || !primaryIndex.isUnique())
+ {
+ ErrorManager.recordOrShowError(11885);
+ // A unique primary index is required in the target table, each field of which is mapped to some source field, in order to do a replace-mode or parent-mode COPY/GET/MERGE/FILL type of operation on a dataset table.
+ return false;
+ }
+
+ StringBuilder sb = new StringBuilder("WHERE ");
+ Iterator it = primaryIndex.components();
+ boolean first = true;
+ while (it.hasNext())
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.append(" and ");
+ }
+
+ P2JIndexComponent comp = it.next();
+ sb.append(dstBuf.getLegacyName()).append(".").append(comp.getLegacyName()).append("=")
+ .append(srcBuf.getLegacyName()).append(".").append(comp.getLegacyName());
+ }
+
+ replaceQuery = sb.toString();
+ }
+ else
+ {
+ FastCopyHelper fastCopyHelper = new FastCopyHelper(simpleCopy,
+ srcBuf,
+ dstBuf,
+ propsMap,
+ propsB4Map,
+ srcB4Buf,
+ dstB4Buf);
+ if (fastCopyHelper.canFastCopy(looseCopy))
+ {
+ destRecId = fastCopyHelper.executeCopy(srcRecId, append);
+ savedAny = true;
+ return true;
+ }
}
// get the list of source records to be copied. These will be all records in
// srcBuf's backing temp table which have srcDMO's multiplex ID.
- HQLExpression buf = new HQLExpression("from ");
- buf.append(srcBuf.getDMOImplementationName());
+ HQLExpression fqlBuilder = new HQLExpression("from ");
+ fqlBuilder.append(srcBuf.getDMOImplementationName());
// srcBuf is a TemporaryBuffer, so it isMultiplexed()
- buf.append(" where ");
- buf.append(MULTIPLEX_FIELD_NAME);
- buf.append(" = ", true);
+ fqlBuilder.append(" where ");
+ fqlBuilder.append(MULTIPLEX_FIELD_NAME);
+ fqlBuilder.append(" = ", true);
// force the records to be ordered by their ID
- // TODO in copy-temp-table sorting order affects which record remains in constraints
- // violation case. The correct sorting is by primary index if it presents or by
- // the index with the lexicographically first name.
- buf.append(" order by ");
- buf.append(MULTIPLEX_FIELD_NAME);
- buf.append(",");
- buf.append(Session.PK);
+ // TODO: in copy-temp-table sorting order affects which record remains in constraints
+ // violation case. The correct sorting is by primary index if it presents or by
+ // the index with the lexicographically first name.
+ fqlBuilder.append(" order by ");
+ fqlBuilder.append(MULTIPLEX_FIELD_NAME);
+ fqlBuilder.append(",");
+ fqlBuilder.append(Session.PK);
- String fql = buf.toFinalExpression();
+ String fql = fqlBuilder.toFinalExpression();
String[] entities = new String[] { srcBuf.getEntityName() };
Object[] parms = new Object[] { srcBuf.getMultiplexID() };
ScrollableResults results =
- persistence.scroll(entities, fql, parms, 0, 0, ResultSet.TYPE_FORWARD_ONLY);
+ persistence.scroll(entities, fql, parms, 0, 0, ResultSet.TYPE_FORWARD_ONLY);
BufferManager bufMgr = srcBuf.getBufferManager();
@@ -3492,8 +3469,30 @@
// copy each source record into a new destination DMO instance
Object[] row = results.get();
TempRecord srcRec = (TempRecord) row[0];
- TempRecord dstRec = dstBuf.instantiateDMO();
- dstRec.initialize(dstBuf.getSession(), false);
+ srcBuf.loadRecord(srcRec);
+ TempRecord dstRec = null;
+ boolean createNew = true;
+
+ if (replaceMode)
+ {
+ logical found = dstBufImpl._find(replaceQuery,
+ BufferImpl.FindMode.UNIQUE,
+ LockType.EXCLUSIVE,
+ srcBufImpl);
+ if (found.booleanValue())
+ {
+ // do not create a new record. An existing record matching the index was found and
+ // loaded into buffer
+ createNew = false;
+ dstRec = ((TempRecord) dstBufImpl.buffer().getCurrentRecord());
+ }
+ }
+
+ if (createNew)
+ {
+ dstRec = dstBuf.instantiateDMO();
+ dstRec.initialize(dstBuf.getSession(), true);
+ }
if (simpleCopy)
{
@@ -3520,7 +3519,6 @@
dstRec.primaryKey(id);
// validate a record against the target table
- // TODO also validate against already copied records
try
{
dstBuf.validate(dstRec);
@@ -3579,6 +3577,11 @@
savedAny = true;
}
}
+ catch (Throwable t) // debug block
+ {
+ t.printStackTrace();
+ throw t;
+ }
finally
{
if (results != null)
@@ -3755,10 +3758,10 @@
TempTable srcTempTable = srcBuf.getParentTable();
TempTable dstTempTable = dstBuf.getParentTable();
-
+
String srcBufName = srcDMO.name().getValue();
String dstBufName = dstDMO.name().getValue();
-
+
String[] srcProperties = TempTableBuilder.getOrderedPropertyNames(srcTempTable.getDMOInterface(),
srcTempTable);
String[] dstProperties = TempTableBuilder.getOrderedPropertyNames(dstTempTable.getDMOInterface(),
@@ -3808,13 +3811,17 @@
}
dstPropName = dstProperties[idx];
}
- else
+ else if (i < dstProperties.length)
{
dstPropName = dstProperties[i];
}
+ else
+ {
+ handleTablesDoNotMatch(errorMode, srcBufName, dstBufName);
+ return null;
+ }
String srcPropName = srcProperties[i];
-
DatumAccess srcDatum = srcBuf.getGetterDatums().get(srcPropName);
DatumAccess dstDatum = dstBuf.getSetterDatums().get(dstPropName);
@@ -3896,287 +3903,6 @@
}
/**
- * Performance improved variant for {@link #copyAllRows}, when we know the destination table can have its
- * records inserted via INSERT INTO ... SELECT FROM, without having to do any validation on
- * the records, as the destination table will accept them all.
- *
- * @param simpleCopy
- * Flag identifying if the source and destination tables have the same structure.
- * @param srcBuf
- * The source buffer.
- * @param dstBuf
- * The destination buffer.
- * @param mapPks
- * Flag indicating if the source and destination primary keys (and the after-rowid,
- * must be computed mapped. Required to cross-reference before-table records or to place the
- * destination buffer on the proper record, if the source buffer has a record loaded.
- * @param propsMap
- * The property mapping.
- * @param sqlIndexName
- * The SQL index name used in SELECT.
- * @param indexName
- * The index name to compute the ORDER BY clause.
- *
- * @return If mapPKs is true, it will return a mapping of source primary keys to the pair
- * of destination primary key and the source record's after-rowid.
- *
- * @throws PersistenceException
- * In case of problems during insert/select/etc.
- */
- private static Map bulkCopyAllRows(boolean simpleCopy,
- TemporaryBuffer srcBuf,
- TemporaryBuffer dstBuf,
- boolean mapPks,
- Map propsMap,
- String sqlIndexName,
- String indexName)
- throws PersistenceException
- {
- Persistence persistence = srcBuf.getPersistence();
-
- DmoMeta srcMeta = srcBuf.getDmoInfo();
- DmoMeta dstMeta = dstBuf.getDmoInfo();
-
- String sequenceName = dstBuf.local.sequenceName;
- String srcTable = srcMeta.getSqlTableName();
- String dstTable = dstMeta.getSqlTableName();
-
- StringBuilder columnListSrc = new StringBuilder("nextval('").append(sequenceName).append("'),")
- .append(dstBuf.getMultiplexID()).append(",")
- .append(ReservedProperty._ERRORFLAG.column).append(",")
- .append(ReservedProperty._ORIGINROWID.column).append(",")
- .append(ReservedProperty._ERRORSTRING.column).append(",")
- .append(ReservedProperty._PEERROWID.column).append(",")
- .append(ReservedProperty._ROWSTATE.column);
- StringBuilder columnListDst = new StringBuilder(Session.PK).append(",")
- .append(MULTIPLEX_FIELD_NAME).append(",")
- .append(ReservedProperty._ERRORFLAG.column).append(",")
- .append(ReservedProperty._ORIGINROWID.column).append(",")
- .append(ReservedProperty._ERRORSTRING.column).append(",")
- .append(ReservedProperty._PEERROWID.column).append(",")
- .append(ReservedProperty._ROWSTATE.column);
-
- Map> extentFields = new HashMap<>();
- if (!simpleCopy)
- {
- for (Map.Entry prop : propsMap.entrySet())
- {
- DatumAccess src = prop.getKey();
- DatumAccess dst = prop.getValue();
- Property srcProp = srcMeta.getFieldInfo(src.getPropertyName());
- Property dstProp = dstMeta.getFieldInfo(dst.getPropertyName());
-
- if (srcProp.extent == 0)
- {
- columnListSrc.append(",").append(srcProp.column);
- columnListDst.append(",").append(dstProp.column);
- if (srcProp._isDatetimeTz)
- {
- // adding support for timezone offset, in case of datetime-tz data
- columnListSrc.append(",").append(srcProp.column).append(DDLGeneratorWorker.DTZ_OFFSET);
- columnListDst.append(",").append(dstProp.column).append(DDLGeneratorWorker.DTZ_OFFSET);
- }
- }
- else
- {
- List extents =
- extentFields.computeIfAbsent(srcProp.extent, k -> new ArrayList<>());
- extents.add(new Property[] { srcProp, dstProp });
- }
- }
- }
- else
- {
- Iterator iter = dstMeta.getFields(false);
- while (iter.hasNext())
- {
- Property prop = iter.next();
- if (prop.extent == 0)
- {
- columnListSrc.append(",").append(prop.column);
- columnListDst.append(",").append(prop.column);
- if (prop._isDatetimeTz)
- {
- // adding support for timezone offset, in case of datetime-tz data
- columnListSrc.append(",").append(prop.column).append(DDLGeneratorWorker.DTZ_OFFSET);
- columnListDst.append(",").append(prop.column).append(DDLGeneratorWorker.DTZ_OFFSET);
- }
- }
- else
- {
- List extents =
- extentFields.computeIfAbsent(prop.extent, k -> new ArrayList<>());
- extents.add(new Property[] { prop, prop });
- }
- }
- }
-
- StringBuilder sort = null;
- if (indexName != null)
- {
- Iterator iter = srcBuf.dmoInfo.getDatabaseIndexes();
- while (iter.hasNext())
- {
- P2JIndex idx = iter.next();
- if (idx.getName().equals(indexName))
- {
- sort = new StringBuilder(MULTIPLEX_FIELD_NAME);
- Iterator iterc = idx.components();
- while (iterc.hasNext())
- {
- P2JIndexComponent idxComp = iterc.next();
- String field = idxComp.getColumnName();
- if (idxComp.isCharType())
- {
- // temp-tables always have computed columns
- field = srcBuf.getDialect().getComputedColumnPrefix(!idxComp.isIgnoreCase()) + field;
- }
- sort.append(",").append(field).append(srcBuf.getDialect().orderByNulls(true));
- }
- if (!idx.isUnique())
- {
- sort.append(",").append(Session.PK);
- }
- break;
- }
- }
-
- if (sort == null)
- {
- throw new PersistenceException("Could not find index for " + indexName);
- }
- }
- if (sort == null)
- {
- sort = new StringBuilder(MULTIPLEX_FIELD_NAME).append(",").append(Session.PK);
- }
-
- Object[] multiplexArg = new Object[] { srcBuf.getMultiplexID() };
- Long firstDstPK = dstBuf.local.getCurrentPrimaryKey(dstBuf.getDMOInterface()) + 1;
- persistence.executeSQL("alter sequence " + sequenceName + " restart with " + firstDstPK);
- String sqlInsert = "insert into " + dstTable + " (" + columnListDst.toString() + ")" +
- " select " + columnListSrc.toString() +
- " from " + srcTable +
- " use index (" + sqlIndexName + ")" +
- " where " + MULTIPLEX_FIELD_NAME + " = ?" +
- " order by " + sort.toString();
-
- int recNum = persistence.executeSQL(sqlInsert, multiplexArg);
-
- Map srcToDstPks = null;
- if (mapPks)
- {
- srcToDstPks = new HashMap<>();
-
- // create the mappings of original PK to new PKs
- String sqlPk = "select " + Session.PK + "," + TempRecord._PEER_ROWID +
- " from " + srcTable +
- " use index (" + sqlIndexName + ")" +
- " where " + MULTIPLEX_FIELD_NAME + " = ?" +
- " order by " + sort;
- ScrollableResults srcPks = persistence.executeSQLQuery(sqlPk, multiplexArg);
- int i = 0;
- while (srcPks.next())
- {
- Long srcPk = srcPks.get(0, Long.class);
- Long srcPeerId = srcPks.get(1, Long.class);
- Long dstPk = firstDstPK + i;
- i = i + 1;
-
- srcToDstPks.put(srcPk, new Long[] { dstPk, srcPeerId });
- }
- }
-
- // update the DMO's primary key generator
- dstBuf.local.setCurrentPrimaryKey(dstBuf.getDMOInterface(), firstDstPK + recNum - 1);
-
- if (!extentFields.isEmpty())
- {
- // insert records in all extent tables
- for (Map.Entry> extent : extentFields.entrySet())
- {
- // the SELECT (part of the INSERT) will not be able to use and INDEX, so we need to fetch all
- // records and use INSERT with multiple rows (plus batch inserts).
-
- // the PARENT__ID will be computed explicitly
- StringBuilder extColumnListSrc = new StringBuilder(ReservedProperty.LIST__INDEX.column);
- StringBuilder extColumnListDst = new StringBuilder();
- extColumnListDst.append(ReservedProperty.PARENT__ID.column).append(",")
- .append(ReservedProperty.LIST__INDEX.column);
- StringBuilder values = new StringBuilder("?, ?"); // PARENT__ID, LIST__INDEX
- for (Property[] props : extent.getValue())
- {
- values.append(", ?");
-
- extColumnListSrc.append(",").append(props[0].column);
- if (props[0]._isDatetimeTz)
- {
- // adding support for timezone offset, in case of datetime-tz data
- extColumnListSrc.append(",").append(props[0].column).append(DDLGeneratorWorker.DTZ_OFFSET);
- }
-
- extColumnListDst.append(",").append(props[1].column);
- if (props[1]._isDatetimeTz)
- {
- // adding support for timezone offset, in case of datetime-tz data
- extColumnListDst.append(",").append(props[1].column).append(DDLGeneratorWorker.DTZ_OFFSET);
- }
- }
-
- String sqlExtSelect =
- " select " + extColumnListSrc.toString() +
- " from " + srcTable + "__" + extent.getKey() +
- " join " + srcTable + " on " + ReservedProperty.PARENT__ID.column + " = " + Session.PK +
- " where " + MULTIPLEX_FIELD_NAME + " = ?" +
- " order by " + sort + ", " +
- ReservedProperty.PARENT__ID.column + ", " +
- ReservedProperty.LIST__INDEX.column;
- ScrollableResults> results = persistence.executeSQLQuery(sqlExtSelect, multiplexArg);
-
- StringBuilder sqlExtInsert = new StringBuilder();
- sqlExtInsert.append("insert into ").append(dstTable).append("__").append(extent.getKey());
- sqlExtInsert.append("(").append(extColumnListDst).append(") values ");
- for (int i = 0; i < extent.getKey(); i++)
- {
- sqlExtInsert.append(i == 0 ? "" : ",").append("(").append(values).append(")");
- }
- int width = 2 + extent.getValue().size();
- long[] nextRecPK = new long[] { firstDstPK };
- Object[] args = new Object[width * extent.getKey()];
-
- Supplier