From 5252b7bd2a1149d67e0896dc568f06c71bc203e4 Mon Sep 17 00:00:00 2001 From: kolchagov Date: Wed, 20 Jul 2016 12:07:09 +0300 Subject: [PATCH 1/3] Added negative _LIMIT to select backwards! --- .gitignore | 3 + WebContent/META-INF/context.xml | 2 + nb-build.xml | 71 + nbproject/ant-deploy.xml | 37 + nbproject/build-impl.xml | 1480 +++++++++++++++++ nbproject/genfiles.properties | 11 + nbproject/project.properties | 141 ++ nbproject/project.xml | 128 ++ nbproject/rest-build.xml | 35 + .../restsql/core/impl/AbstractSqlBuilder.java | 910 +++++----- 10 files changed, 2373 insertions(+), 445 deletions(-) create mode 100644 .gitignore create mode 100644 WebContent/META-INF/context.xml create mode 100644 nb-build.xml create mode 100644 nbproject/ant-deploy.xml create mode 100644 nbproject/build-impl.xml create mode 100644 nbproject/genfiles.properties create mode 100644 nbproject/project.properties create mode 100644 nbproject/project.xml create mode 100644 nbproject/rest-build.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a568390 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/nbproject/private/ +/build/ +/dist/ \ No newline at end of file diff --git a/WebContent/META-INF/context.xml b/WebContent/META-INF/context.xml new file mode 100644 index 0000000..fd2fac1 --- /dev/null +++ b/WebContent/META-INF/context.xml @@ -0,0 +1,2 @@ + + diff --git a/nb-build.xml b/nb-build.xml new file mode 100644 index 0000000..773cfe2 --- /dev/null +++ b/nb-build.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + Builds, tests, and runs the project restsql. + + + diff --git a/nbproject/ant-deploy.xml b/nbproject/ant-deploy.xml new file mode 100644 index 0000000..5ca2a42 --- /dev/null +++ b/nbproject/ant-deploy.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 0000000..768cdc5 --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1480 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set build.dir + Must set build.web.dir + Must set build.generated.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.war + + + + + + + + + +The Java EE server classpath is not correctly set up - server home directory is missing. +Either open the project in the IDE and assign the server or setup the server classpath manually. +For example like this: + ant -Dj2ee.server.home=<app_server_installation_directory> + + +The Java EE server classpath is not correctly set up. Your active server type is ${j2ee.server.type}. +Either open the project in the IDE and assign the server or setup the server classpath manually. +For example like this: + ant -Duser.properties.file=<path_to_property_file> (where you put the property "j2ee.platform.classpath" in a .properties file) +or ant -Dj2ee.platform.classpath=<server_classpath> (where no properties file is used) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +The libs.CopyLibs.classpath property is not set up. +This property must point to +org-netbeans-modules-java-j2seproject-copylibstask.jar file which is part +of NetBeans IDE installation and is usually located at +<netbeans_installation>/java<version>/ant/extra folder. +Either open the project in the IDE and make sure CopyLibs library +exists or setup the property manually. For example like this: + ant -Dlibs.CopyLibs.classpath=a/path/to/org-netbeans-modules-java-j2seproject-copylibstask.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.jsp.includes + + + + + + + + + + + + + + + + + + + + + + + + + + Must select a file in the IDE or set jsp.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Browser not found, cannot launch the deployed application. Try to set the BROWSER environment variable. + + + Launching ${browse.url} + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 0000000..5561e35 --- /dev/null +++ b/nbproject/genfiles.properties @@ -0,0 +1,11 @@ +nb-build.xml.data.CRC32=da7d5556 +nb-build.xml.script.CRC32=f43a1d9c +nb-build.xml.stylesheet.CRC32=651128d4@1.75.1.1 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=da7d5556 +nbproject/build-impl.xml.script.CRC32=e5382079 +nbproject/build-impl.xml.stylesheet.CRC32=99ea4b56@1.75.1.1 +nbproject/rest-build.xml.data.CRC32=f249d521 +nbproject/rest-build.xml.script.CRC32=7bb95d99 +nbproject/rest-build.xml.stylesheet.CRC32=0cfeebcc@1.30.1 diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..7286a12 --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,141 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=true +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +auxiliary.org-netbeans-modules-projectimport-eclipse-core.key=src=src;output=obj/bin;web=/WebContent;context=restsql; +auxiliary.org-netbeans-modules-projectimport-eclipse-core.project=. +auxiliary.org-netbeans-modules-projectimport-eclipse-core.timestamp=1469002680030 +build.classes.dir=${build.web.dir}/WEB-INF/classes +build.classes.excludes=**/*.java,**/*.form +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +build.web.dir=${build.dir}/web +build.web.excludes=${build.classes.excludes} +buildfile=nb-build.xml +client.urlPart= +compile.jsps=false +conf.dir=WebContent/conf +debug.classpath=${build.classes.dir}:${javac.classpath} +debug.test.classpath=\ + ${run.test.classpath} +display.browser=true +dist.dir=dist +dist.ear.war=${dist.dir}/${war.ear.name} +dist.javadoc.dir=${dist.dir}/javadoc +dist.war=${dist.dir}/${war.name} +excludes= +file.reference.asm-3.1.jar=WebContent/WEB-INF/lib/asm-3.1.jar +file.reference.commons-lang-2.6.jar=WebContent/WEB-INF/lib/commons-lang-2.6.jar +file.reference.commons-logging-1.1.1.jar=WebContent/WEB-INF/lib/commons-logging-1.1.1.jar +file.reference.gmetric4j-1.0.7.jar=WebContent/WEB-INF/lib/gmetric4j-1.0.7.jar +file.reference.jackson-annotations-2.5.0.jar=WebContent/WEB-INF/lib/jackson-annotations-2.5.0.jar +file.reference.jackson-core-2.5.0.jar=WebContent/WEB-INF/lib/jackson-core-2.5.0.jar +file.reference.jackson-databind-2.5.0.jar=WebContent/WEB-INF/lib/jackson-databind-2.5.0.jar +file.reference.jersey-core-1.5.jar=WebContent/WEB-INF/lib/jersey-core-1.5.jar +file.reference.jersey-server-1.5.jar=WebContent/WEB-INF/lib/jersey-server-1.5.jar +file.reference.json_simple-1.1-src.jar=WebContent/WEB-INF/lib/json_simple-1.1-src.jar +file.reference.json_simple-1.1.jar=WebContent/WEB-INF/lib/json_simple-1.1.jar +file.reference.jsr311-api-1.1.1.jar=WebContent/WEB-INF/lib/jsr311-api-1.1.1.jar +file.reference.log4j-1.2.16.jar=WebContent/WEB-INF/lib/log4j-1.2.16.jar +file.reference.metrics-annotation-3.1.0.jar=WebContent/WEB-INF/lib/metrics-annotation-3.1.0.jar +file.reference.metrics-core-3.1.0.jar=WebContent/WEB-INF/lib/metrics-core-3.1.0.jar +file.reference.metrics-ganglia-3.1.0.jar=WebContent/WEB-INF/lib/metrics-ganglia-3.1.0.jar +file.reference.metrics-graphite-3.1.0.jar=WebContent/WEB-INF/lib/metrics-graphite-3.1.0.jar +file.reference.metrics-healthchecks-3.1.0.jar=WebContent/WEB-INF/lib/metrics-healthchecks-3.1.0.jar +file.reference.metrics-jersey-3.1.0.jar=WebContent/WEB-INF/lib/metrics-jersey-3.1.0.jar +file.reference.metrics-json-3.1.0.jar=WebContent/WEB-INF/lib/metrics-json-3.1.0.jar +file.reference.metrics-jvm-3.1.0.jar=WebContent/WEB-INF/lib/metrics-jvm-3.1.0.jar +file.reference.metrics-servlets-3.1.0.jar=WebContent/WEB-INF/lib/metrics-servlets-3.1.0.jar +file.reference.mysql-connector-java-5.1.14-bin.jar=WebContent/WEB-INF/lib/mysql-connector-java-5.1.14-bin.jar +file.reference.oncrpc-1.0.7.jar=WebContent/WEB-INF/lib/oncrpc-1.0.7.jar +file.reference.postgresql-9.0-801.jdbc4.jar=WebContent/WEB-INF/lib/postgresql-9.0-801.jdbc4.jar +file.reference.restsql-src=src +file.reference.slf4j-api-1.7.10.jar=WebContent/WEB-INF/lib/slf4j-api-1.7.10.jar +file.reference.slf4j-simple-1.7.10.jar=WebContent/WEB-INF/lib/slf4j-simple-1.7.10.jar +includes=** +j2ee.compile.on.save=true +j2ee.copy.static.files.on.save=true +j2ee.deploy.on.save=true +j2ee.platform=1.5 +j2ee.platform.classpath=${j2ee.server.home}/lib/annotations-api.jar:${j2ee.server.home}/lib/catalina-ant.jar:${j2ee.server.home}/lib/catalina-ha.jar:${j2ee.server.home}/lib/catalina-storeconfig.jar:${j2ee.server.home}/lib/catalina-tribes.jar:${j2ee.server.home}/lib/catalina.jar:${j2ee.server.home}/lib/ecj-4.4.jar:${j2ee.server.home}/lib/el-api.jar:${j2ee.server.home}/lib/jasper-el.jar:${j2ee.server.home}/lib/jasper.jar:${j2ee.server.home}/lib/jsp-api.jar:${j2ee.server.home}/lib/servlet-api.jar:${j2ee.server.home}/lib/tomcat-api.jar:${j2ee.server.home}/lib/tomcat-coyote.jar:${j2ee.server.home}/lib/tomcat-dbcp.jar:${j2ee.server.home}/lib/tomcat-i18n-es.jar:${j2ee.server.home}/lib/tomcat-i18n-fr.jar:${j2ee.server.home}/lib/tomcat-i18n-ja.jar:${j2ee.server.home}/lib/tomcat-jdbc.jar:${j2ee.server.home}/lib/tomcat-jni.jar:${j2ee.server.home}/lib/tomcat-spdy.jar:${j2ee.server.home}/lib/tomcat-util-scan.jar:${j2ee.server.home}/lib/tomcat-util.jar:${j2ee.server.home}/lib/tomcat-websocket.jar:${j2ee.server.home}/lib/websocket-api.jar +j2ee.server.type=Tomcat +jar.compress=false +java.source.based=true +javac.classpath=\ + ${file.reference.metrics-json-3.1.0.jar}:\ + ${file.reference.commons-logging-1.1.1.jar}:\ + ${file.reference.gmetric4j-1.0.7.jar}:\ + ${file.reference.oncrpc-1.0.7.jar}:\ + ${file.reference.jersey-core-1.5.jar}:\ + ${file.reference.jersey-server-1.5.jar}:\ + ${file.reference.metrics-graphite-3.1.0.jar}:\ + ${file.reference.metrics-core-3.1.0.jar}:\ + ${file.reference.asm-3.1.jar}:\ + ${file.reference.metrics-annotation-3.1.0.jar}:\ + ${file.reference.metrics-servlets-3.1.0.jar}:\ + ${file.reference.postgresql-9.0-801.jdbc4.jar}:\ + ${file.reference.log4j-1.2.16.jar}:\ + ${file.reference.metrics-healthchecks-3.1.0.jar}:\ + ${file.reference.commons-lang-2.6.jar}:\ + ${file.reference.mysql-connector-java-5.1.14-bin.jar}:\ + ${file.reference.slf4j-simple-1.7.10.jar}:\ + ${file.reference.jackson-annotations-2.5.0.jar}:\ + ${file.reference.metrics-jvm-3.1.0.jar}:\ + ${file.reference.slf4j-api-1.7.10.jar}:\ + ${file.reference.jackson-databind-2.5.0.jar}:\ + ${file.reference.metrics-jersey-3.1.0.jar}:\ + ${file.reference.jackson-core-2.5.0.jar}:\ + ${file.reference.json_simple-1.1.jar}:\ + ${file.reference.metrics-ganglia-3.1.0.jar}:\ + ${file.reference.jsr311-api-1.1.1.jar}:\ + ${file.reference.json_simple-1.1-src.jar} +# Space-separated list of extra javac options +javac.compilerargs= +javac.debug=true +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.6 +javac.target=1.6 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.preview=true +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +lib.dir=WebContent/WEB-INF/lib +no.dependencies=false +persistence.xml.dir=${conf.dir} +platform.active=default_platform +resource.dir=setup +rest.config.type=ide +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +# Space-separated list of JVM arguments used when running a class with a main method or a unit test +# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value): +runmain.jvmargs= +source.encoding=UTF-8 +source.root=. +src.dir=${file.reference.restsql-src} +test.src.dir= +war.content.additional= +war.ear.name=${war.name} +war.name=restsql.war +web.docbase.dir=WebContent +webinf.dir=WebContent/WEB-INF diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..c3afbf1 --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,128 @@ + + + org.netbeans.modules.web.project + + + + + + restsql + 1.6.5 + + + ${file.reference.metrics-json-3.1.0.jar} + WEB-INF/lib + + + ${file.reference.commons-logging-1.1.1.jar} + WEB-INF/lib + + + ${file.reference.gmetric4j-1.0.7.jar} + WEB-INF/lib + + + ${file.reference.oncrpc-1.0.7.jar} + WEB-INF/lib + + + ${file.reference.jersey-core-1.5.jar} + WEB-INF/lib + + + ${file.reference.jersey-server-1.5.jar} + WEB-INF/lib + + + ${file.reference.metrics-graphite-3.1.0.jar} + WEB-INF/lib + + + ${file.reference.metrics-core-3.1.0.jar} + WEB-INF/lib + + + ${file.reference.asm-3.1.jar} + WEB-INF/lib + + + ${file.reference.metrics-annotation-3.1.0.jar} + WEB-INF/lib + + + ${file.reference.metrics-servlets-3.1.0.jar} + WEB-INF/lib + + + ${file.reference.postgresql-9.0-801.jdbc4.jar} + WEB-INF/lib + + + ${file.reference.log4j-1.2.16.jar} + WEB-INF/lib + + + ${file.reference.metrics-healthchecks-3.1.0.jar} + WEB-INF/lib + + + ${file.reference.commons-lang-2.6.jar} + WEB-INF/lib + + + ${file.reference.mysql-connector-java-5.1.14-bin.jar} + WEB-INF/lib + + + ${file.reference.slf4j-simple-1.7.10.jar} + WEB-INF/lib + + + ${file.reference.jackson-annotations-2.5.0.jar} + WEB-INF/lib + + + ${file.reference.metrics-jvm-3.1.0.jar} + WEB-INF/lib + + + ${file.reference.slf4j-api-1.7.10.jar} + WEB-INF/lib + + + ${file.reference.jackson-databind-2.5.0.jar} + WEB-INF/lib + + + ${file.reference.metrics-jersey-3.1.0.jar} + WEB-INF/lib + + + ${file.reference.jackson-core-2.5.0.jar} + WEB-INF/lib + + + ${file.reference.json_simple-1.1.jar} + WEB-INF/lib + + + ${file.reference.metrics-ganglia-3.1.0.jar} + WEB-INF/lib + + + ${file.reference.jsr311-api-1.1.1.jar} + WEB-INF/lib + + + ${file.reference.json_simple-1.1-src.jar} + WEB-INF/lib + + + + + + + + + + diff --git a/nbproject/rest-build.xml b/nbproject/rest-build.xml new file mode 100644 index 0000000..aa01f4b --- /dev/null +++ b/nbproject/rest-build.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/org/restsql/core/impl/AbstractSqlBuilder.java b/src/org/restsql/core/impl/AbstractSqlBuilder.java index 2582c6c..285758d 100644 --- a/src/org/restsql/core/impl/AbstractSqlBuilder.java +++ b/src/org/restsql/core/impl/AbstractSqlBuilder.java @@ -18,451 +18,471 @@ /** * Builds SQL for an operation on a SQL Resource. - * + * * @author Mark Sawers */ - public abstract class AbstractSqlBuilder implements SqlBuilder { - private static final int DEFAULT_DELETE_SIZE = 100; - private static final int DEFAULT_INSERT_SIZE = 300; - private static final int DEFAULT_SELECT_SIZE = 300; - private static final int DEFAULT_UPDATE_SIZE = 300; - - // Public methods - - /** Creates select SQL. */ - @Override - public SqlStruct buildSelectSql(final SqlResourceMetaData metaData, final String mainSql, - final Request request) throws InvalidRequestException { - final SqlStruct sql = new SqlStruct(mainSql.length(), DEFAULT_SELECT_SIZE); - sql.getMain().append(mainSql); - buildSelectSql(metaData, request.getResourceIdentifiers(), sql); - buildSelectSql(metaData, request.getParameters(), sql); - addOrderBy(metaData, sql); - - // Handle limit and offset - if (request.getSelectLimit() != null) { - // Call concrete database-specific class to get the limit clause - sql.appendToBothClauses(buildSelectLimitSql(request.getSelectLimit().intValue(), request - .getSelectOffset().intValue())); - } - - sql.compileStatements(); - return sql; - } - - /** Creates update, insert or delete SQL. */ - @Override - public Map buildWriteSql(final SqlResourceMetaData metaData, final Request request, - final boolean doParent) throws InvalidRequestException { - Map sqls = null; - switch (request.getType()) { - case INSERT: - sqls = buildInsertSql(metaData, request, doParent); - break; - case UPDATE: - sqls = buildUpdateSql(metaData, request, doParent); - break; - case DELETE: - sqls = buildDeleteSql(metaData, request, doParent); - break; - default: - throw new InvalidRequestException("SELECT Request provided to SqlBuilder.buildWriteSql()"); - } - return sqls; - } - - /** Creates select SQL limit clause. Returns empty string if database does not support limit feature. */ - protected abstract String buildSelectLimitSql(final int limit, final int offset); - - /** Enables override for databases like PostgreSQL that need special handling for enumerations. */ - protected String buildPreparedParameterSql(final ColumnMetaData column) { - return "?"; - } - - // Private helper methods - - /** Adds order by statement . */ - private void addOrderBy(final SqlResourceMetaData metaData, final SqlStruct sql) { - boolean firstColumn = true; - firstColumn = addOrderByColumn(metaData, sql, firstColumn, metaData.getParent()); - addOrderByColumn(metaData, sql, firstColumn, metaData.getChild()); - } - - /** Adds order by column list for the table's primary keys. */ - private boolean addOrderByColumn(final SqlResourceMetaData metaData, final SqlStruct sql, - boolean firstColumn, final TableMetaData table) { - if (table != null) { - for (final ColumnMetaData column : table.getPrimaryKeys()) { - if (firstColumn) { - sql.appendToBothClauses(" ORDER BY "); - firstColumn = false; - } else { - sql.appendToBothClauses(", "); - } - sql.appendToBothClauses(column.getQualifiedColumnName()); - } - } - return firstColumn; - } - - private void appendToBoth(final SqlStruct sql, final boolean useMain, final String string) { - if (useMain) { - sql.appendToBothMains(string); - } else { - sql.appendToBothClauses(string); - } - } - - private void appendValue(final StringBuilder part, final StringBuilder preparedPart, - final List preparedValues, final Object value, final boolean charOrDateTimeType, - ColumnMetaData column) { - if (value != null && charOrDateTimeType) { - part.append('\''); - } - part.append(value); - if (value != null && charOrDateTimeType) { - part.append('\''); - } - preparedPart.append(buildPreparedParameterSql(column)); - preparedValues.add(value); - } - - private Map buildDeleteSql(final SqlResourceMetaData metaData, final Request request, - final boolean doParent) throws InvalidRequestException { - final Map sqls = new HashMap(metaData.getNumberTables()); - buildDeleteSqlPart(metaData, request.getResourceIdentifiers(), sqls, doParent); - buildDeleteSqlPart(metaData, request.getParameters(), sqls, doParent); - - for (final String tableName : sqls.keySet()) { - final SqlStruct sql = sqls.get(tableName); - if (sql == null) { - sqls.remove(tableName); - } else { - sql.compileStatements(); - } - } - - if (sqls.size() == 0 && doParent) { - throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); - } - return sqls; - } - - private void buildDeleteSqlPart(final SqlResourceMetaData metaData, - final List requestParams, final Map sqls, final boolean doParent) - throws InvalidRequestException { - if (requestParams != null) { - for (final RequestValue requestParam : requestParams) { - final List tables = metaData.getWriteTables(Request.Type.DELETE, doParent); - for (final TableMetaData table : tables) { - final ColumnMetaData column = table.getColumns().get(requestParam.getName()); - if (column != null) { - if (column.isReadOnly()) { - throw new InvalidRequestException(InvalidRequestException.MESSAGE_READONLY_PARAM, - column.getColumnLabel()); - } - final String qualifiedTableName = column.getQualifiedTableName(); - SqlStruct sql = sqls.get(qualifiedTableName); - if (sql == null) { - // Create new sql holder - sql = new SqlStruct(DEFAULT_DELETE_SIZE, DEFAULT_DELETE_SIZE / 2); - sqls.put(qualifiedTableName, sql); - sql.getMain().append("DELETE FROM "); - sql.getMain().append(qualifiedTableName); - sql.appendToBothClauses(" WHERE "); - } else { - sql.appendToBothClauses(" AND "); - } - setNameValue(Request.Type.DELETE, metaData, column, requestParam, true, sql, false); - } - } - } - } - } - - /** - * Builds insert SQL. - * - * @param params insert params - * @return map of sql struct, per table - * @throws InvalidRequestException if a database access error occurs - */ - private Map buildInsertSql(final SqlResourceMetaData metaData, final Request request, - final boolean doParent) throws InvalidRequestException { - - final Map sqls = new HashMap(metaData.getNumberTables()); - - // Iterate through the params and build the sql for each table - for (final RequestValue param : request.getParameters()) { - final List tables = metaData.getWriteTables(request.getType(), doParent); - for (final TableMetaData table : tables) { - final ColumnMetaData column = table.getColumns().get(param.getName()); - if (column != null) { - if (column.isReadOnly()) { - throw new InvalidRequestException(InvalidRequestException.MESSAGE_READONLY_PARAM, - column.getColumnLabel()); - } - final String qualifiedTableName = column.getQualifiedTableName(); - SqlStruct sql = sqls.get(qualifiedTableName); - if (sql == null) { - // Create new sql holder - sql = new SqlStruct(DEFAULT_INSERT_SIZE, DEFAULT_INSERT_SIZE / 2); - sqls.put(qualifiedTableName, sql); - sql.getMain().append("INSERT INTO "); - sql.getMain().append(qualifiedTableName); - sql.getMain().append(" ("); - - sql.appendToBothClauses(" VALUES ("); - } else { - sql.getMain().append(','); - sql.appendToBothClauses(","); - } - sql.getMain().append(column.getColumnName()); // since parameter may use column label - - // Begin quote the column value - if (column.isCharOrDateTimeType() && param.getValue() != null) { - sql.getClause().append('\''); - } - - // Convert String to appropriate object - column.normalizeValue(param); - - // Set the value in the printable clause, the ? in the prepared clause, and prepared clause value - sql.getClause().append(param.getValue()); - sql.getPreparedClause().append(buildPreparedParameterSql(column)); - sql.getPreparedValues().add(param.getValue()); - - // End quote the column value - if (column.isCharOrDateTimeType() && param.getValue() != null) { - sql.getClause().append('\''); - } - } - } - } - - for (final String tableName : sqls.keySet()) { - final SqlStruct sql = sqls.get(tableName); - if (sql == null) { - sqls.remove(tableName); - } else { - sql.getMain().append(')'); - sql.appendToBothClauses(")"); - sql.compileStatements(); - } - } - - if (sqls.size() == 0) { - throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); - } - return sqls; - } - - private void buildSelectSql(final SqlResourceMetaData metaData, final List params, - final SqlStruct sql) throws InvalidRequestException { - if (params != null && params.size() > 0) { - boolean validParamFound = false; - for (final RequestValue param : params) { - if (sql.getMain().indexOf("where ") > 0 || sql.getMain().indexOf("WHERE ") > 0 - || sql.getClause().length() != 0) { - sql.appendToBothClauses(" AND "); - } else { - sql.appendToBothClauses(" WHERE "); - } - - for (final TableMetaData table : metaData.getTables()) { - final ColumnMetaData column = table.getColumns().get(param.getName()); - if (column != null) { - if (column.isReadOnly()) { - throw new InvalidRequestException(InvalidRequestException.MESSAGE_READONLY_PARAM, - column.getColumnLabel()); - } - if (!column.isNonqueriedForeignKey()) { - validParamFound = true; - setNameValue(Request.Type.SELECT, metaData, column, param, true, sql, false); - } - } - } - } - - if (sql.getClause().length() > 0 && !validParamFound) { - throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); - } - } - } - - private Map buildUpdateSql(final SqlResourceMetaData metaData, final Request request, - final boolean doParent) throws InvalidRequestException { - final Map sqls = new HashMap(metaData.getNumberTables()); - - List resIds; - if (metaData.isHierarchical() && !doParent) { - // Clone the list, since changing the request will affect the next child request - resIds = new ArrayList(request.getResourceIdentifiers().size()); - for (final RequestValue resId : request.getResourceIdentifiers()) { - resIds.add(resId); - } - } else { // is flat or is hierarchical and executing the parent - resIds = request.getResourceIdentifiers(); - } - - final List tables = metaData.getWriteTables(request.getType(), doParent); - - boolean validParamFound = false; - for (final RequestValue param : request.getParameters()) { - for (final TableMetaData table : tables) { - final ColumnMetaData column = table.getColumns().get(param.getName()); - if (column != null) { - if (column.isReadOnly()) { - throw new InvalidRequestException(InvalidRequestException.MESSAGE_READONLY_PARAM, - column.getColumnLabel()); - } - if (column.isPrimaryKey()) { - // Add this to the res Ids - assume resIds is non null - resIds.add(param); - } else if (!column.isNonqueriedForeignKey()) { - SqlStruct sql = sqls.get(column.getQualifiedTableName()); - if (sql == null) { - // Create new sql holder - sql = new SqlStruct(DEFAULT_UPDATE_SIZE, DEFAULT_UPDATE_SIZE / 2, true); - sqls.put(column.getQualifiedTableName(), sql); - sql.appendToBothMains("UPDATE "); - sql.appendToBothMains(column.getQualifiedTableName()); - sql.appendToBothMains(" SET "); - } else { - sql.appendToBothMains(","); - } - - validParamFound = true; - setNameValue(request.getType(), metaData, column, param, false, sql, true); - } - } - } - } - - if (!validParamFound) { - throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); - } - validParamFound = false; - - for (final String qualifiedTableName : sqls.keySet()) { - final SqlStruct sql = sqls.get(qualifiedTableName); - if (sql == null) { - sqls.remove(qualifiedTableName); - } else { - // Iterate through the resourceIds and build the where clause sql for each table - for (final RequestValue resId : resIds) { - final TableMetaData table = metaData.getTableMap().get(qualifiedTableName); - final ColumnMetaData column = table.getColumns().get(resId.getName()); - if (column != null) { - if (sql.getClause().length() == 0) { - sql.appendToBothClauses(" WHERE "); - } else { // sql.getClause().length() > 0 - sql.appendToBothClauses(" AND "); - } - validParamFound = true; - setNameValue(request.getType(), metaData, column, resId, true, sql, false); - } - } - sql.compileStatements(); - } - } - - if (!validParamFound) { - throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); - } - return sqls; - } - - private boolean containsWildcard(final Object value) { - boolean contains = false; - if (value != null && value instanceof String) { - final int index = ((String) value).indexOf("%"); - contains = index > -1; - } - return contains; - } - - /** - * Adds the SQL selector for the parameter pair with an appropriate operator (=, >, <, >=, <=, LIKE or IN). - * - * @throws InvalidRequestException if unexpected operator is found (Escaped is only for internal use) - */ - private void setNameValue(final Type requestType, final SqlResourceMetaData metaData, - final ColumnMetaData column, final RequestValue param, final boolean columnIsSelector, - final SqlStruct sql, final boolean useMain) throws InvalidRequestException { - - // Convert String to Number object if required - column.normalizeValue(param); - - // Append the name - if (requestType == Request.Type.SELECT) { - appendToBoth(sql, useMain, column.getQualifiedColumnName()); - } else { - appendToBoth(sql, useMain, column.getColumnName()); - } - - // Append the operator - if (columnIsSelector && param.getOperator() == Operator.Equals && containsWildcard(param.getValue())) { - appendToBoth(sql, useMain, " LIKE "); - } else if (!columnIsSelector && requestType == Request.Type.UPDATE - && param.getOperator() == Operator.IsNull) { - appendToBoth(sql, useMain, " = "); - } else { - switch (param.getOperator()) { - case Equals: - appendToBoth(sql, useMain, " = "); - break; - case In: - appendToBoth(sql, useMain, " IN "); - break; - case IsNull: - appendToBoth(sql, useMain, " IS NULL"); - break; - case IsNotNull: - appendToBoth(sql, useMain, " IS NOT NULL"); - break; - case LessThan: - appendToBoth(sql, useMain, " < "); - break; - case LessThanOrEqualTo: - appendToBoth(sql, useMain, " <= "); - break; - case GreaterThan: - appendToBoth(sql, useMain, " > "); - break; - case GreaterThanOrEqualTo: - appendToBoth(sql, useMain, " >= "); - break; - case NotEquals: - appendToBoth(sql, useMain, " != "); - break; - default: // case Escaped - throw new InvalidRequestException( - "SqlBuilder.setNameValue() found unexpected operator of type " - + param.getOperator()); - } - } - - // Append the value - if (param.getOperator() == Operator.In) { - appendToBoth(sql, useMain, "("); - boolean firstValue = true; - for (final Object value : param.getInValues()) { - if (!firstValue) { - appendToBoth(sql, useMain, ","); - } - appendValue(useMain ? sql.getMain() : sql.getClause(), - useMain ? sql.getPreparedMain() : sql.getPreparedClause(), sql.getPreparedValues(), - value, column.isCharOrDateTimeType(), column); - firstValue = false; - } - appendToBoth(sql, useMain, ")"); - } else if ((param.getOperator() != Operator.IsNull && param.getOperator() != Operator.IsNotNull) - || (!columnIsSelector && requestType == Request.Type.UPDATE)) { - appendValue(useMain ? sql.getMain() : sql.getClause(), - useMain ? sql.getPreparedMain() : sql.getPreparedClause(), sql.getPreparedValues(), - param.getValue(), column.isCharOrDateTimeType(), column); - } - } -} \ No newline at end of file + + private static final int DEFAULT_DELETE_SIZE = 100; + private static final int DEFAULT_INSERT_SIZE = 300; + private static final int DEFAULT_SELECT_SIZE = 300; + private static final int DEFAULT_UPDATE_SIZE = 300; + + // Public methods + /** + * Creates select SQL. Added by Kolchagov: support negative limit (select + * backwards) + */ + @Override + public SqlStruct buildSelectSql(final SqlResourceMetaData metaData, final String mainSql, + final Request request) throws InvalidRequestException { + final SqlStruct sql = new SqlStruct(mainSql.length(), DEFAULT_SELECT_SIZE); + sql.getMain().append(mainSql); + buildSelectSql(metaData, request.getResourceIdentifiers(), sql); + buildSelectSql(metaData, request.getParameters(), sql); + final boolean hasLimit = request.getSelectLimit() != null; + final boolean isNegativeLimit = hasLimit && request.getSelectLimit().intValue() < 0; + addOrderBy(metaData, sql, isNegativeLimit); + + // Handle limit and offset + if (hasLimit) { + final int limit = Math.abs(request.getSelectLimit().intValue()); + // Call concrete database-specific class to get the limit clause + sql.appendToBothClauses(buildSelectLimitSql(limit, request + .getSelectOffset().intValue())); + } + + sql.compileStatements(); + return sql; + } + + /** + * Creates update, insert or delete SQL. + */ + @Override + public Map buildWriteSql(final SqlResourceMetaData metaData, final Request request, + final boolean doParent) throws InvalidRequestException { + Map sqls = null; + switch (request.getType()) { + case INSERT: + sqls = buildInsertSql(metaData, request, doParent); + break; + case UPDATE: + sqls = buildUpdateSql(metaData, request, doParent); + break; + case DELETE: + sqls = buildDeleteSql(metaData, request, doParent); + break; + default: + throw new InvalidRequestException("SELECT Request provided to SqlBuilder.buildWriteSql()"); + } + return sqls; + } + + /** + * Creates select SQL limit clause. Returns empty string if database does + * not support limit feature. + */ + protected abstract String buildSelectLimitSql(final int limit, final int offset); + + /** + * Enables override for databases like PostgreSQL that need special handling + * for enumerations. + */ + protected String buildPreparedParameterSql(final ColumnMetaData column) { + return "?"; + } + + // Private helper methods + /** + * Adds order by statement . + */ + private void addOrderBy(final SqlResourceMetaData metaData, final SqlStruct sql, boolean isDescending) { + boolean firstColumn = true; + firstColumn = addOrderByColumn(metaData, sql, firstColumn, metaData.getParent(), isDescending); + addOrderByColumn(metaData, sql, firstColumn, metaData.getChild(), isDescending); + } + + /** + * Adds order by column list for the table's primary keys. + */ + private boolean addOrderByColumn(final SqlResourceMetaData metaData, final SqlStruct sql, boolean firstColumn, final TableMetaData table, boolean isDescending) { + if (table != null) { + for (final ColumnMetaData column : table.getPrimaryKeys()) { + if (firstColumn) { + sql.appendToBothClauses(" ORDER BY "); + firstColumn = false; + } else { + sql.appendToBothClauses(", "); + } + sql.appendToBothClauses(column.getQualifiedColumnName()); + } + if (isDescending) { + sql.appendToBothClauses(" DESC"); + } + } + return firstColumn; + } + + private void appendToBoth(final SqlStruct sql, final boolean useMain, final String string) { + if (useMain) { + sql.appendToBothMains(string); + } else { + sql.appendToBothClauses(string); + } + } + + private void appendValue(final StringBuilder part, final StringBuilder preparedPart, + final List preparedValues, final Object value, final boolean charOrDateTimeType, + ColumnMetaData column) { + if (value != null && charOrDateTimeType) { + part.append('\''); + } + part.append(value); + if (value != null && charOrDateTimeType) { + part.append('\''); + } + preparedPart.append(buildPreparedParameterSql(column)); + preparedValues.add(value); + } + + private Map buildDeleteSql(final SqlResourceMetaData metaData, final Request request, + final boolean doParent) throws InvalidRequestException { + final Map sqls = new HashMap(metaData.getNumberTables()); + buildDeleteSqlPart(metaData, request.getResourceIdentifiers(), sqls, doParent); + buildDeleteSqlPart(metaData, request.getParameters(), sqls, doParent); + + for (final String tableName : sqls.keySet()) { + final SqlStruct sql = sqls.get(tableName); + if (sql == null) { + sqls.remove(tableName); + } else { + sql.compileStatements(); + } + } + + if (sqls.size() == 0 && doParent) { + throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); + } + return sqls; + } + + private void buildDeleteSqlPart(final SqlResourceMetaData metaData, + final List requestParams, final Map sqls, final boolean doParent) + throws InvalidRequestException { + if (requestParams != null) { + for (final RequestValue requestParam : requestParams) { + final List tables = metaData.getWriteTables(Request.Type.DELETE, doParent); + for (final TableMetaData table : tables) { + final ColumnMetaData column = table.getColumns().get(requestParam.getName()); + if (column != null) { + if (column.isReadOnly()) { + throw new InvalidRequestException(InvalidRequestException.MESSAGE_READONLY_PARAM, + column.getColumnLabel()); + } + final String qualifiedTableName = column.getQualifiedTableName(); + SqlStruct sql = sqls.get(qualifiedTableName); + if (sql == null) { + // Create new sql holder + sql = new SqlStruct(DEFAULT_DELETE_SIZE, DEFAULT_DELETE_SIZE / 2); + sqls.put(qualifiedTableName, sql); + sql.getMain().append("DELETE FROM "); + sql.getMain().append(qualifiedTableName); + sql.appendToBothClauses(" WHERE "); + } else { + sql.appendToBothClauses(" AND "); + } + setNameValue(Request.Type.DELETE, metaData, column, requestParam, true, sql, false); + } + } + } + } + } + + /** + * Builds insert SQL. + * + * @param params insert params + * @return map of sql struct, per table + * @throws InvalidRequestException if a database access error occurs + */ + private Map buildInsertSql(final SqlResourceMetaData metaData, final Request request, + final boolean doParent) throws InvalidRequestException { + + final Map sqls = new HashMap(metaData.getNumberTables()); + + // Iterate through the params and build the sql for each table + for (final RequestValue param : request.getParameters()) { + final List tables = metaData.getWriteTables(request.getType(), doParent); + for (final TableMetaData table : tables) { + final ColumnMetaData column = table.getColumns().get(param.getName()); + if (column != null) { + if (column.isReadOnly()) { + throw new InvalidRequestException(InvalidRequestException.MESSAGE_READONLY_PARAM, + column.getColumnLabel()); + } + final String qualifiedTableName = column.getQualifiedTableName(); + SqlStruct sql = sqls.get(qualifiedTableName); + if (sql == null) { + // Create new sql holder + sql = new SqlStruct(DEFAULT_INSERT_SIZE, DEFAULT_INSERT_SIZE / 2); + sqls.put(qualifiedTableName, sql); + sql.getMain().append("INSERT INTO "); + sql.getMain().append(qualifiedTableName); + sql.getMain().append(" ("); + + sql.appendToBothClauses(" VALUES ("); + } else { + sql.getMain().append(','); + sql.appendToBothClauses(","); + } + sql.getMain().append(column.getColumnName()); // since parameter may use column label + + // Begin quote the column value + if (column.isCharOrDateTimeType() && param.getValue() != null) { + sql.getClause().append('\''); + } + + // Convert String to appropriate object + column.normalizeValue(param); + + // Set the value in the printable clause, the ? in the prepared clause, and prepared clause value + sql.getClause().append(param.getValue()); + sql.getPreparedClause().append(buildPreparedParameterSql(column)); + sql.getPreparedValues().add(param.getValue()); + + // End quote the column value + if (column.isCharOrDateTimeType() && param.getValue() != null) { + sql.getClause().append('\''); + } + } + } + } + + for (final String tableName : sqls.keySet()) { + final SqlStruct sql = sqls.get(tableName); + if (sql == null) { + sqls.remove(tableName); + } else { + sql.getMain().append(')'); + sql.appendToBothClauses(")"); + sql.compileStatements(); + } + } + + if (sqls.size() == 0) { + throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); + } + return sqls; + } + + private void buildSelectSql(final SqlResourceMetaData metaData, final List params, + final SqlStruct sql) throws InvalidRequestException { + if (params != null && params.size() > 0) { + boolean validParamFound = false; + for (final RequestValue param : params) { + if (sql.getMain().indexOf("where ") > 0 || sql.getMain().indexOf("WHERE ") > 0 + || sql.getClause().length() != 0) { + sql.appendToBothClauses(" AND "); + } else { + sql.appendToBothClauses(" WHERE "); + } + + for (final TableMetaData table : metaData.getTables()) { + final ColumnMetaData column = table.getColumns().get(param.getName()); + if (column != null) { + if (column.isReadOnly()) { + throw new InvalidRequestException(InvalidRequestException.MESSAGE_READONLY_PARAM, + column.getColumnLabel()); + } + if (!column.isNonqueriedForeignKey()) { + validParamFound = true; + setNameValue(Request.Type.SELECT, metaData, column, param, true, sql, false); + } + } + } + } + + if (sql.getClause().length() > 0 && !validParamFound) { + throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); + } + } + } + + private Map buildUpdateSql(final SqlResourceMetaData metaData, final Request request, + final boolean doParent) throws InvalidRequestException { + final Map sqls = new HashMap(metaData.getNumberTables()); + + List resIds; + if (metaData.isHierarchical() && !doParent) { + // Clone the list, since changing the request will affect the next child request + resIds = new ArrayList(request.getResourceIdentifiers().size()); + for (final RequestValue resId : request.getResourceIdentifiers()) { + resIds.add(resId); + } + } else { // is flat or is hierarchical and executing the parent + resIds = request.getResourceIdentifiers(); + } + + final List tables = metaData.getWriteTables(request.getType(), doParent); + + boolean validParamFound = false; + for (final RequestValue param : request.getParameters()) { + for (final TableMetaData table : tables) { + final ColumnMetaData column = table.getColumns().get(param.getName()); + if (column != null) { + if (column.isReadOnly()) { + throw new InvalidRequestException(InvalidRequestException.MESSAGE_READONLY_PARAM, + column.getColumnLabel()); + } + if (column.isPrimaryKey()) { + // Add this to the res Ids - assume resIds is non null + resIds.add(param); + } else if (!column.isNonqueriedForeignKey()) { + SqlStruct sql = sqls.get(column.getQualifiedTableName()); + if (sql == null) { + // Create new sql holder + sql = new SqlStruct(DEFAULT_UPDATE_SIZE, DEFAULT_UPDATE_SIZE / 2, true); + sqls.put(column.getQualifiedTableName(), sql); + sql.appendToBothMains("UPDATE "); + sql.appendToBothMains(column.getQualifiedTableName()); + sql.appendToBothMains(" SET "); + } else { + sql.appendToBothMains(","); + } + + validParamFound = true; + setNameValue(request.getType(), metaData, column, param, false, sql, true); + } + } + } + } + + if (!validParamFound) { + throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); + } + validParamFound = false; + + for (final String qualifiedTableName : sqls.keySet()) { + final SqlStruct sql = sqls.get(qualifiedTableName); + if (sql == null) { + sqls.remove(qualifiedTableName); + } else { + // Iterate through the resourceIds and build the where clause sql for each table + for (final RequestValue resId : resIds) { + final TableMetaData table = metaData.getTableMap().get(qualifiedTableName); + final ColumnMetaData column = table.getColumns().get(resId.getName()); + if (column != null) { + if (sql.getClause().length() == 0) { + sql.appendToBothClauses(" WHERE "); + } else { // sql.getClause().length() > 0 + sql.appendToBothClauses(" AND "); + } + validParamFound = true; + setNameValue(request.getType(), metaData, column, resId, true, sql, false); + } + } + sql.compileStatements(); + } + } + + if (!validParamFound) { + throw new InvalidRequestException(InvalidRequestException.MESSAGE_INVALID_PARAMS); + } + return sqls; + } + + private boolean containsWildcard(final Object value) { + boolean contains = false; + if (value != null && value instanceof String) { + final int index = ((String) value).indexOf("%"); + contains = index > -1; + } + return contains; + } + + /** + * Adds the SQL selector for the parameter pair with an appropriate operator + * (=, >, <, >=, <=, LIKE or IN). + * + * @throws InvalidRequestException if unexpected operator is found (Escaped + * is only for internal use) + */ + private void setNameValue(final Type requestType, final SqlResourceMetaData metaData, + final ColumnMetaData column, final RequestValue param, final boolean columnIsSelector, + final SqlStruct sql, final boolean useMain) throws InvalidRequestException { + + // Convert String to Number object if required + column.normalizeValue(param); + + // Append the name + if (requestType == Request.Type.SELECT) { + appendToBoth(sql, useMain, column.getQualifiedColumnName()); + } else { + appendToBoth(sql, useMain, column.getColumnName()); + } + + // Append the operator + if (columnIsSelector && param.getOperator() == Operator.Equals && containsWildcard(param.getValue())) { + appendToBoth(sql, useMain, " LIKE "); + } else if (!columnIsSelector && requestType == Request.Type.UPDATE + && param.getOperator() == Operator.IsNull) { + appendToBoth(sql, useMain, " = "); + } else { + switch (param.getOperator()) { + case Equals: + appendToBoth(sql, useMain, " = "); + break; + case In: + appendToBoth(sql, useMain, " IN "); + break; + case IsNull: + appendToBoth(sql, useMain, " IS NULL"); + break; + case IsNotNull: + appendToBoth(sql, useMain, " IS NOT NULL"); + break; + case LessThan: + appendToBoth(sql, useMain, " < "); + break; + case LessThanOrEqualTo: + appendToBoth(sql, useMain, " <= "); + break; + case GreaterThan: + appendToBoth(sql, useMain, " > "); + break; + case GreaterThanOrEqualTo: + appendToBoth(sql, useMain, " >= "); + break; + case NotEquals: + appendToBoth(sql, useMain, " != "); + break; + default: // case Escaped + throw new InvalidRequestException( + "SqlBuilder.setNameValue() found unexpected operator of type " + + param.getOperator()); + } + } + + // Append the value + if (param.getOperator() == Operator.In) { + appendToBoth(sql, useMain, "("); + boolean firstValue = true; + for (final Object value : param.getInValues()) { + if (!firstValue) { + appendToBoth(sql, useMain, ","); + } + appendValue(useMain ? sql.getMain() : sql.getClause(), + useMain ? sql.getPreparedMain() : sql.getPreparedClause(), sql.getPreparedValues(), + value, column.isCharOrDateTimeType(), column); + firstValue = false; + } + appendToBoth(sql, useMain, ")"); + } else if ((param.getOperator() != Operator.IsNull && param.getOperator() != Operator.IsNotNull) + || (!columnIsSelector && requestType == Request.Type.UPDATE)) { + appendValue(useMain ? sql.getMain() : sql.getClause(), + useMain ? sql.getPreparedMain() : sql.getPreparedClause(), sql.getPreparedValues(), + param.getValue(), column.isCharOrDateTimeType(), column); + } + } +} From 0df8871f069dd63e04d48f118b310f37cb5fe02e Mon Sep 17 00:00:00 2001 From: kolchagov Date: Tue, 26 Jul 2016 00:06:34 +0300 Subject: [PATCH 2/3] Added support for views - substituted as RO tables. Views can be used to implement currently unsupported aggregate functions. Fixed default config properties read incorrectly --- src/org/restsql/core/Config.java | 2 +- src/org/restsql/core/impl/AbstractSqlResourceMetaData.java | 2 +- .../tools/impl/mysql/MySqlResourceDefinitionGenerator.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/restsql/core/Config.java b/src/org/restsql/core/Config.java index d4c0ea2..229eca7 100644 --- a/src/org/restsql/core/Config.java +++ b/src/org/restsql/core/Config.java @@ -185,7 +185,7 @@ public static void loadAllProperties() { if (file.exists()) { inputStream = new FileInputStream(file); } else { - inputStream = Config.class.getResourceAsStream(restsqlPropertiesFileName); + inputStream = Config.class.getResourceAsStream(DEFAULT_RESTSQL_PROPERTIES); } if (inputStream != null) { properties.backingProperties.load(inputStream); diff --git a/src/org/restsql/core/impl/AbstractSqlResourceMetaData.java b/src/org/restsql/core/impl/AbstractSqlResourceMetaData.java index 35fc475..cd72e70 100644 --- a/src/org/restsql/core/impl/AbstractSqlResourceMetaData.java +++ b/src/org/restsql/core/impl/AbstractSqlResourceMetaData.java @@ -417,7 +417,7 @@ protected String getSqlMainQuery(final SqlResourceDefinition definition, final S */ protected boolean isColumnReadOnly(final ResultSetMetaData resultSetMetaData, final int colNumber) throws SQLException { - return resultSetMetaData.isReadOnly(colNumber); + return resultSetMetaData.isReadOnly(colNumber) || "".equals(resultSetMetaData.getCatalogName(colNumber)); } /** diff --git a/src/org/restsql/tools/impl/mysql/MySqlResourceDefinitionGenerator.java b/src/org/restsql/tools/impl/mysql/MySqlResourceDefinitionGenerator.java index 96ee327..d055cb2 100644 --- a/src/org/restsql/tools/impl/mysql/MySqlResourceDefinitionGenerator.java +++ b/src/org/restsql/tools/impl/mysql/MySqlResourceDefinitionGenerator.java @@ -9,7 +9,7 @@ * @author Mark Sawers */ public class MySqlResourceDefinitionGenerator extends AbstractResourceDefinitionGenerator { - private static final String SQL_COLUMNS_QUERY = "select column_name, table_name from information_schema.columns where table_schema = ? and table_name not in (select table_name from information_schema.views)"; + private static final String SQL_COLUMNS_QUERY = "select column_name, table_name from information_schema.columns where table_schema = ? "; @Override public String getColumnsQuery() { From 16acf4c8d35fc8c2dcb1e80deac6a893cb7bb633 Mon Sep 17 00:00:00 2001 From: kolchagov Date: Tue, 26 Jul 2016 11:45:00 +0300 Subject: [PATCH 3/3] Fixed RO columns can't have query parameters for views. --- src/org/restsql/core/impl/AbstractSqlBuilder.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/restsql/core/impl/AbstractSqlBuilder.java b/src/org/restsql/core/impl/AbstractSqlBuilder.java index 285758d..7fdbc72 100644 --- a/src/org/restsql/core/impl/AbstractSqlBuilder.java +++ b/src/org/restsql/core/impl/AbstractSqlBuilder.java @@ -291,10 +291,11 @@ private void buildSelectSql(final SqlResourceMetaData metaData, final List