Skip to content

Commit

Permalink
Merge pull request #2315 from qorelanguage/feature/2314_sqlutil_custo…
Browse files Browse the repository at this point in the history
…m_cops

implemented #2314: SqlUtil: add support for custom column operators
  • Loading branch information
davidnich committed Oct 21, 2017
2 parents 0a0618f + 8eef3a5 commit 0acf0e5
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 11 deletions.
2 changes: 2 additions & 0 deletions doxygen/lang/900_release_notes.dox.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
- fixed value setting to process all remaining arguments on the command line (<a href="https://github.com/qorelanguage/qore/issues/2294">issue 2294</a>)
- <a href="../../modules/Qdx/html/index.html">Qdx</a> module fixes
- fixed a bug in documentation post-processing for @ref hashdecl "hashdecl" declarations (<a href="https://github.com/qorelanguage/qore/issues/2298">issue 2298</a>)
- <q href="../../modules/SqlUtil/html.indexhtml">SqlUtil</a> module changes
- implemented support for user custom column operators (<a href="https://github.com/qorelanguage/qore/issues/2314">issue 2314</a>)
- added missing comparison methods in the <a href="../../modules/QUnit/html/index.html">QUnit</a> module (<a href="https://github.com/qorelanguage/qore/issues/1588">issue 1588</a>):
- \c Test::assertRegex()
- \c Test::assertNRegex()
Expand Down
41 changes: 41 additions & 0 deletions examples/test/qlib/SqlUtil/OracleSqlUtil.qtest
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ class OracleTest inherits SqlTestBase {
addTestCase("named types to align", \testNTYAlignment());
addTestCase("bug 1684 'driver' column name", \testDriverColumnName());
addTestCase("analytic/window functions", \testAnalyticFunctions());
addTestCase("custom column operators #2314", \testCustomColumnOperators());
}

set_return_value(main());
Expand Down Expand Up @@ -344,6 +345,46 @@ class OracleTest inherits SqlTestBase {
assertEq(True, exists c, "column 'driver' must exist in the table");
}

testCustomColumnOperators() {
if (!table)
testSkip("no DB connection");

# negative test
try {
table.addCustomCopOperator("as", hash());
}
catch (hash ex) {
assertEq("CUSTOM-OPERATOR-ERROR", ex.err, "Operator 'as' must be already registered");
}

# regular test
hash to_char = (
"code" : string sub(string arg1, auto arg) {
return sprintf("to_char(%s, '%s')", arg1, arg);
},
);

table.addCustomCopOperator("to_char", to_char);

hash sh = (
"columns" : ( cop_as(SqlUtil::make_cop("to_char", "date_f", "yyyymmdd"), "date_to_char"),
# test oracle specific operator because I changed getColumnOperatorMap too
cop_year("date_f"),
),
"limit" : 1,
);

string sql;
on_exit {
if (m_options.verbose) {
printf("custom op sql: %s\n", sql);
}
}
hash res = table.selectRow(sh, \sql);

assertEq("20160111", res.date_to_char, "Date format test from to_char");
}

private string getResultSetSql() {
return "begin open :rs for select id, def_test1, def_test2 from oracle_test; end;";
}
Expand Down
4 changes: 2 additions & 2 deletions qlib/FreetdsSqlUtil.qm
Original file line number Diff line number Diff line change
Expand Up @@ -1160,7 +1160,7 @@ any v = c.name;
);

#! column operator specializations for FreeTDS
const FreetdsCopMap = DefaultCopMap + (
const FreetdsCopMap = (
COP_CAST: (
"code": string sub (string cve, list args) {
string name = QoreTypeMap{args[0]} ?? args[0];
Expand Down Expand Up @@ -1469,7 +1469,7 @@ any v = c.name;
}

#! returns the column operator map for this object
private hash getColumnOperatorMap() {
private hash getColumnOperatorMapImpl() {
return FreetdsCopMap;
}

Expand Down
4 changes: 2 additions & 2 deletions qlib/MysqlSqlUtil.qm
Original file line number Diff line number Diff line change
Expand Up @@ -1219,7 +1219,7 @@ end";
const MysqlAlignTableOptions = AbstractTable::AlignTableOptions + MysqlTableCreationOptions;

#! column operator specializations for MySQL
const MysqlCopMap = DefaultCopMap + (
const MysqlCopMap = (
COP_CAST: (
"code": string sub (string cve, list args) {
string name = QoreTypeMap{args[0]} ?? args[0];
Expand Down Expand Up @@ -1350,7 +1350,7 @@ end";
}

#! returns the column operator map for this object
private hash getColumnOperatorMap() {
private hash getColumnOperatorMapImpl() {
return MysqlCopMap;
}

Expand Down
4 changes: 2 additions & 2 deletions qlib/OracleSqlUtil.qm
Original file line number Diff line number Diff line change
Expand Up @@ -1904,7 +1904,7 @@ auto v = c.name;
);

#! column operator specializations for Oracle
const OracleCopMap = DefaultCopMap + (
const OracleCopMap = (
COP_CAST: (
"code": string sub (string cve, list args) {
string name = QoreTypeMap{args[0]} ?? args[0];
Expand Down Expand Up @@ -2704,7 +2704,7 @@ auto v = c.name;
}

#! returns the column operator map for this object
private hash getColumnOperatorMap() {
private hash getColumnOperatorMapImpl() {
return OracleCopMap;
}

Expand Down
4 changes: 2 additions & 2 deletions qlib/PgsqlSqlUtil.qm
Original file line number Diff line number Diff line change
Expand Up @@ -1369,7 +1369,7 @@ auto v = c.name;
const PgsqlAlignTableOptions = AbstractTable::AlignTableOptions + PgsqlTableCreationOptions;

#! column operator specializations for PostgreSQL
const PgsqlCopMap = DefaultCopMap + (
const PgsqlCopMap = (
COP_CAST: (
"code": string sub (string cve, list args) {
string name = QoreTypeMap{args[0]} ?? args[0];
Expand Down Expand Up @@ -1579,7 +1579,7 @@ auto v = c.name;
}

#! returns the column operator map for this object
private hash getColumnOperatorMap() {
private hash getColumnOperatorMapImpl() {
return PgsqlCopMap;
}

Expand Down
76 changes: 73 additions & 3 deletions qlib/SqlUtil.qm
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
%enable-all-warnings

module SqlUtil {
version = "1.4";
version = "1.5";
desc = "user module for working with SQL data";
author = "David Nichols <david@qore.org>";
url = "http://qore.org";
Expand Down Expand Up @@ -100,6 +100,9 @@ module SqlUtil {

@section sqlutil_relnotes Release Notes for the SqlUtil Module

@subsection sqlutilv1_5 SqlUtil v1.5
- implemented support for user custom column operators (<a href="https://github.com/qorelanguage/qore/issues/2314">issue 2314</a>)

@subsection sqlutilv1_4 SqlUtil v1.4
- fixed a bug in update and upsert statement generation when the given data does not have enough columns to use the unique index found, an error message is generated that contains all the columns names instead of just the column names required by the index (<a href="https://github.com/qorelanguage/qore/issues/1013">issue 1013</a>)
- implemented @ref cop_trunc_date() operator (<a href="https://github.com/qorelanguage/qore/issues/2032">issue 2032</a>)
Expand Down Expand Up @@ -8757,6 +8760,8 @@ Table table("pgsql:user/pass@db%host", "table");
bool inDb = False;
#! manual edits
bool manual = False;

hash m_customCopMap = hash();
}

#! creates the object; private constructor
Expand Down Expand Up @@ -14612,10 +14617,75 @@ string sql = table.getAlignSqlString(table2);
}

#! returns the column operator map for this object
/** override in subclasses to return driver-specific options
/** subclasses should to implement getColumnOperatorMapImpl() to return driver-specific options
*/
private hash getColumnOperatorMap() {
return DefaultCopMap;
return DefaultCopMap + getColumnOperatorMapImpl()
+ m_customCopMap;
}

#! Reimplement in subclasses to provide driver specific column operators
private *hash getColumnOperatorMapImpl() {
}

#! register custom user column operator for this table object
/** This method allows to register custom operators for select
statements.

@param name a string with operator name. It has to be unique in the driver
@param operator an operator has as expected by @ref SqlUtil::make_cop() function

@throw CUSTOM-OPERATOR-ERROR in case when user tries to register
already existing operator with \c name or if the \c operator
hash does not contain \c "code" key/value.

@code{.py}
# t is a AbstractTable/Table object
# custom operator hash
hash to_char = (
"code" : string sub(string arg1, auto arg) {
return sprintf("to_char(%s, '%s')", arg1, arg);
},
);
# operator registration
t.addCustomCopOperator("to_char", to_char);
# example usage
hash sh = (
"columns" : cop_as(SqlUtil::make_cop("to_char", "d", "yyyymmddhh24miss"), "string_fmt_date"),
"limit" : 1,
);
# output
string sql;
on_exit printf("SQL> %s\n", sql);
any res = t.selectRows(sh, \sql);
# result (from Oracle)
# list: (1 element)
# [0]=hash: (3 members)
# string_fmt_date : "20171020071854"
# SQL> select to_char(d, 'yyyymmddhh24miss') as string_fmt_date from test_schema.t
@endcode
*/
addCustomCopOperator(string name, hash operator) {
hash ops = getColumnOperatorMap();
if (ops.hasKey(name)) {
throw "CUSTOM-OPERATOR-ERROR",
sprintf("Operator '%s' is already registered as: %y", name, ops{name});
}

# input hash validations
if (!operator.hasKey("code")) {
throw "CUSTOM-OPERATOR-ERROR",
"Operator hash must contain key 'code'";
}
else if (operator."code".typeCode() != NT_CLOSURE
&& operator."code".typeCode() != NT_CALLREF
)
{
throw "CUSTOM-OPERATOR-ERROR",
sprintf("Operator hash must contain key 'code' wit value of type 'closure' or 'callref', got: %s", operator."code".type());
}

m_customCopMap{name} = operator;
}

#! returns the insert operator map for this object
Expand Down

0 comments on commit 0acf0e5

Please sign in to comment.