Skip to content

Commit

Permalink
refs #4528 added search operator information to dat provider information
Browse files Browse the repository at this point in the history
refs #4529 fixed type handling with complex hash - string|ist
refs #4531 fixed a bug in the cast<>() operator with hashdecls where a parse-time exception could be thrown for an operation that could succeed at runtime
  • Loading branch information
davidnich committed May 16, 2022
1 parent eb952ae commit 8676140
Show file tree
Hide file tree
Showing 23 changed files with 235 additions and 83 deletions.
7 changes: 7 additions & 0 deletions doxygen/lang/900_release_notes.dox.tmpl
Expand Up @@ -9,6 +9,8 @@

@subsection qore_1_7_3_bug_fixes Bug Fixes in Qore
- <a href="../../modules/DataProvider/html/index.html">DataProvider</a> module
- addded \c search_operators to the data provider response to report supported search operators
(<a href="https://github.com/qorelanguage/qore/issues/4528">issue 4528</a>)
- fixed a type error in \c DataProvider::search*() calls with an empty \c where hash argument
(<a href="https://github.com/qorelanguage/qore/issues/4511">issue 4511</a>)
- <a href="../../modules/RestClient/html/index.html">RestClient</a> module
Expand All @@ -33,6 +35,11 @@
- fixed a bug where \c object types in Swagger schemas could be converted directly to \c object types in %Qore
instead of \c hash
(<a href="https://github.com/qorelanguage/qore/issues/4520">issue 4520</a>)
- fixed a bug where the @ref cast "cast<>() operator" threw parse-time exceptions with hashdecls that could
succeed at runtime
(<a href="https://github.com/qorelanguage/qore/issues/4531">issue 4531</a>)
- fixed a bug handling types at parse time with the @ref minus_operator "minus operator" and complex hash operands
(<a href="https://github.com/qorelanguage/qore/issues/4529">issue 4529</a>)
- fixed a static memory leak in libqore shutting down the library when built with openssl 3+
(<a href="https://github.com/qorelanguage/qore/issues/4522">issue 4522</a>)
- fixed a bug where HTTP redirect messages were encoded twice causing redirect failures in cases where the
Expand Down
5 changes: 0 additions & 5 deletions examples/test/qore/misc/hashdecl.qtest
Expand Up @@ -182,11 +182,6 @@ class HashDeclTest inherits QUnit::Test {
assertEq("str", h.b);
}

{
Program p(PO_NEW_STYLE);
assertThrows("PARSE-EXCEPTION", "hashdecl", \p.parse(), ("sub t() {hash<Container> c1; hash<Container2> c2(); c1 = cast<hash<Container>>(c2);}", ""));
}

{
Program p(PO_NEW_STYLE);
assertThrows("PARSE-TYPE-ERROR", "assignment", \p.parse(), ("sub t() {hash<string, int> h = new hash<Container>();}", ""));
Expand Down
10 changes: 10 additions & 0 deletions examples/test/qore/operators/operators.qtest
Expand Up @@ -39,6 +39,7 @@ class DataTest {

class Test inherits QUnit::Test {
constructor() : QUnit::Test("operators", "1.0", \ARGV) {
addTestCase("minor hash test", \minusHashTest());
addTestCase("push unshift tests", \pushUnshiftTests());
addTestCase("basic operator tests", \basicTests());
addTestCase("date tests", \dateTests());
Expand Down Expand Up @@ -67,6 +68,15 @@ class Test inherits QUnit::Test {
set_return_value(main());
}

minusHashTest() {
hash<string, hash<StatInfo>> h0 = {
"a": <StatInfo>{},
"b": <StatInfo>{},
};
hash<string, hash<StatInfo>> h1 = h0 - 'a';
assertEq("hash<string, hash<StatInfo>>", h1.fullType());
}

pushUnshiftTests() {
# issue #3317: ensure typed and untyped lists are handled equally with push and unshift
{
Expand Down
28 changes: 16 additions & 12 deletions lib/QoreMinusOperatorNode.cpp
Expand Up @@ -141,27 +141,31 @@ int QoreMinusOperatorNode::parseInitImpl(QoreValue& val, QoreParseContext& parse

// if either side is a date, then the return type is date (highest priority)
if (QoreTypeInfo::isType(leftTypeInfo, NT_DATE)
|| QoreTypeInfo::isType(rightTypeInfo, NT_DATE))
|| QoreTypeInfo::isType(rightTypeInfo, NT_DATE)) {
returnTypeInfo = dateTypeInfo;
// otherwise we have to make sure types are known on both sides of the expression
else if (QoreTypeInfo::hasType(leftTypeInfo) && QoreTypeInfo::hasType(rightTypeInfo)) {
} else if (QoreTypeInfo::hasType(leftTypeInfo) && QoreTypeInfo::hasType(rightTypeInfo)) {
if (QoreTypeInfo::isType(leftTypeInfo, NT_NUMBER)
|| QoreTypeInfo::isType(rightTypeInfo, NT_NUMBER))
|| QoreTypeInfo::isType(rightTypeInfo, NT_NUMBER)) {
returnTypeInfo = numberTypeInfo;
else if (QoreTypeInfo::isType(leftTypeInfo, NT_FLOAT)
|| QoreTypeInfo::isType(rightTypeInfo, NT_FLOAT))
} else if (QoreTypeInfo::isType(leftTypeInfo, NT_FLOAT)
|| QoreTypeInfo::isType(rightTypeInfo, NT_FLOAT)) {
returnTypeInfo = floatTypeInfo;
else if (QoreTypeInfo::isType(leftTypeInfo, NT_INT)
|| QoreTypeInfo::isType(rightTypeInfo, NT_INT))
} else if (QoreTypeInfo::isType(leftTypeInfo, NT_INT)
|| QoreTypeInfo::isType(rightTypeInfo, NT_INT)) {
returnTypeInfo = bigIntTypeInfo;
else if ((QoreTypeInfo::isType(leftTypeInfo, NT_HASH)
|| QoreTypeInfo::isType(leftTypeInfo, NT_OBJECT))
} else if (QoreTypeInfo::isType(leftTypeInfo, NT_HASH)
&& (QoreTypeInfo::isType(rightTypeInfo, NT_STRING)
|| QoreTypeInfo::isType(rightTypeInfo, NT_LIST))) {
returnTypeInfo = leftTypeInfo;
} else if (QoreTypeInfo::isType(leftTypeInfo, NT_OBJECT)
&& (QoreTypeInfo::isType(rightTypeInfo, NT_STRING)
|| QoreTypeInfo::isType(rightTypeInfo, NT_LIST)))
returnTypeInfo = hashTypeInfo;
else if (QoreTypeInfo::returnsSingle(leftTypeInfo) && QoreTypeInfo::returnsSingle(rightTypeInfo))
|| QoreTypeInfo::isType(rightTypeInfo, NT_LIST))) {
returnTypeInfo = autoHashTypeInfo;
} else if (QoreTypeInfo::returnsSingle(leftTypeInfo) && QoreTypeInfo::returnsSingle(rightTypeInfo)) {
// only return type nothing if both types are available and return a single type
returnTypeInfo = nothingTypeInfo;
}
}

parse_context.typeInfo = returnTypeInfo;
Expand Down
3 changes: 2 additions & 1 deletion lib/TypedHashDecl.cpp
Expand Up @@ -139,8 +139,9 @@ int typed_hash_decl_private::parseCheckHashDeclAssignment(const QoreProgramLocat
for (auto& i : hd.members.member_list) {
HashDeclMemberInfo* m = members.find(i.first);
if (!m) {
if (!strict_check && QoreTypeInfo::parseReturns(i.second->getTypeInfo(), NT_NOTHING))
if (!strict_check) {
continue;
}
parse_error(*loc, "hashdecl '%s' cannot be initialized from %s with hashdecl '%s' due to key '%s' " \
"present in hashdecl '%s' but not in the target hashdecl '%s'", name.c_str(), context,
hd.name.c_str(), i.first, hd.name.c_str(), name.c_str());
Expand Down
1 change: 1 addition & 0 deletions qlib/CsvUtil/CsvReadDataProvider.qc
Expand Up @@ -36,6 +36,7 @@ public class CsvReadDataProvider inherits DataProvider::AbstractDataProvider {
"has_record": True,
"constructor_options": ConstructorOptions,
"search_options": GenericRecordSearchOptions,
"search_operators": AbstractDataProvider::GenericRecordSearchOperators,
};

#! Constructor options
Expand Down
90 changes: 90 additions & 0 deletions qlib/DataProvider/AbstractDataProvider.qc
Expand Up @@ -58,6 +58,18 @@ public hashdecl DataProviderOptionInfo {
string desc;
}

#! Data provider search operator info
public hashdecl DataProviderSearchOperatorInfo {
#! The option value type or types
softlist<AbstractDataProviderType> type;

#! The search operator display name
string name;

#! The option description
string desc;
}

#! Data provider info
public hashdecl DataProviderInfo {
#! Data provider name
Expand Down Expand Up @@ -257,6 +269,13 @@ public hashdecl DataProviderInfo {
*/
*hash<string, hash<DataProviderOptionInfo>> delete_field_options;

#! Search operators supported by the data provider
/** @note this is a static attribute; it is the same for all data providers of the same type

@since DataProvider 2.3
*/
*hash<string, hash<DataProviderSearchOperatorInfo>> search_operators;

#! A hash of mapper key information
/** @note
- this is a static attribute; it is the same for all data providers of the same type
Expand All @@ -278,6 +297,77 @@ public hashdecl DataProviderInfo {
#! The AbstractDataProvider class
public class AbstractDataProvider {
public {
#! Generic search operators
/** The following are generic search operators implemented for all data providers that do not provide any
native search functionality
*/
const GenericRecordSearchOperators = {
DP_SEARCH_OP_EQ: <DataProviderSearchOperatorInfo>{
"name": "equals (=)",
"type": AbstractDataProviderType::get(AbstractDataProviderType::anyType),
"desc": "a value for equality comparisons; the type of the value should correspond to the field type",
},
DP_SEARCH_OP_NE: <DataProviderSearchOperatorInfo>{
"name": "not equals (!=)",
"type": AbstractDataProviderType::get(AbstractDataProviderType::anyType),
"desc": "a value for not=equals comparisons; the type of the value should correspond to the field "
"type",
},
DP_SEARCH_OP_LT: <DataProviderSearchOperatorInfo>{
"name": "less than (<)",
"type": AbstractDataProviderType::get(AbstractDataProviderType::anyType),
"desc": "a value for less than comparisons; if the field value is less than the argument, then the "
"operation returns true; the type of the value should correspond to the field type",
},
DP_SEARCH_OP_LE: <DataProviderSearchOperatorInfo>{
"name": "less than or equals (<=)",
"type": AbstractDataProviderType::get(AbstractDataProviderType::anyType),
"desc": "a value for less than or equals comparisons; if the field value is less than or equal to "
"the argument, then the operation returns true; the type of the value should correspond to the "
"field type",
},
DP_SEARCH_OP_GT: <DataProviderSearchOperatorInfo>{
"name": "greater than (>)",
"type": AbstractDataProviderType::get(AbstractDataProviderType::anyType),
"desc": "a value for less than comparisons; if the field value is less than the argument, then the "
"operation returns true; the type of the value should correspond to the field type",
},
DP_SEARCH_OP_GE: <DataProviderSearchOperatorInfo>{
"name": "greater than or equals (>=)",
"type": AbstractDataProviderType::get(AbstractDataProviderType::anyType),
"desc": "a value for greater than or equals comparisons; if the field value is greater than or equal "
"to the argument, then the operation returns true; the type of the value should correspond to "
"the field type",
},
DP_SEARCH_OP_BETWEEN: <DataProviderSearchOperatorInfo>{
"name": "between",
"type": AbstractDataProviderType::get(AutoListType),
"desc": "A list with two elements giving the lower and upper bounds of the field value; the list "
"element value types must be equal to the field's type",
},
DP_SEARCH_OP_IN: <DataProviderSearchOperatorInfo>{
"name": "in",
"type": AbstractDataProviderType::get(AutoListType),
"desc": "A list giving possible values of the field; if the field's value matches any of the values "
"in the list, then the operation returns true; element value types must be equal to the field's "
"type",
},
DP_SEARCH_OP_NOT: <DataProviderSearchOperatorInfo>{
"name": "logical not (!)",
"type": AbstractDataProviderType::get(Reflection::NothingType),
"desc": "This operator takes no arguments and reverses the logical value of the field value or "
"implicit operand",
},
DP_SEARCH_OP_REGEX: <DataProviderSearchOperatorInfo>{
"name": "regular expression match",
"type": (new HashDataType())
.addField(new QoreDataField("pattern", "the regular expression pattern to apply on the field "
"value", StringType))
.addField(new QoreDataField("options", "a bitfield of regular expression options to use; ex: "
"1: case-insensitive, 2: multi line matches, 4: dot matches newlines", IntType)),
"desc": "a value for equality comparisons; the type of the value should correspond to the field type",
},
};
}

private {
Expand Down
2 changes: 1 addition & 1 deletion qlib/DataProvider/DataProvider.qc
Expand Up @@ -372,7 +372,7 @@ DbDataProvider db = DataProvider::getFactoryObjectFromStringUseEnv("db{oracle:us
# strip types
hash info = info0;
foreach hash<auto> i0 in (info.pairIterator()) {
if (i0.value.typeCode() == NT_HASH && i0.key =~ /_options/) {
if (i0.value.typeCode() == NT_HASH && i0.key =~ /_(options|operators)/) {
# if the info key value is a hash with a "type" key, then return the type name instead of the type object
map info{i0.key}{$1.key}.type = (map $1.getName(), $1.value.type),
i0.value.pairIterator(),
Expand Down
6 changes: 4 additions & 2 deletions qlib/DataProvider/DataProvider.qm
Expand Up @@ -38,7 +38,7 @@
%requires(reexport) MapperUtil

module DataProvider {
version = "2.2.2";
version = "2.3";
desc = "user module for data access and introspection";
author = "David Nichols <david@qore.org>";
url = "http://qore.org";
Expand Down Expand Up @@ -202,7 +202,9 @@ $env:QORE_DATA_PROVIDERS="MyConnectionProvider;OtherConnectionProvider"

@section dataprovider_relnotes Release Notes

@subsection dataprovider_v2_2_2 DataProvider v2.2.2
@subsection dataprovider_v2_3 DataProvider v2.3
- addded \c search_operators to the data provider response to report supported search operators
(<a href="https://github.com/qorelanguage/qore/issues/4528">issue 4528</a>)
- fixed a type error in \c DataProvider::search*() calls with an empty \c where hash argument
(<a href="https://github.com/qorelanguage/qore/issues/4511">issue 4511</a>)

Expand Down
3 changes: 2 additions & 1 deletion qlib/DataProvider/HashDataType.qc
Expand Up @@ -188,13 +188,14 @@ public class HashDataType inherits QoreDataType {
}

#! adds a field to the type
addField(AbstractDataField field) {
HashDataType addField(AbstractDataField field) {
fields{field.getName()} = field;
# do not allow unnamed fields if any fields are added manually and no default other field type has been set
# manually
if (default_other_field_type && !manual_default_other_field_type) {
remove default_other_field_type;
}
return self;
}

#! Returns the given field, if present, or @ref nothing if not
Expand Down
16 changes: 16 additions & 0 deletions qlib/DbDataProvider/DbTableDataProvider.qc
Expand Up @@ -47,6 +47,7 @@ public class DbTableDataProvider inherits AbstractDataProvider {
"search_options": SearchOptions,
"create_options": CreateOptions,
"upsert_options": UpsertOptions,
"search_operators": (AbstractDataProvider::GenericRecordSearchOperators - DP_SEARCH_OP_REGEX),
"transaction_management": True,
"has_record": True,
"mapper_keys": MapperKeyInfo,
Expand Down Expand Up @@ -252,6 +253,21 @@ public class DbTableDataProvider inherits AbstractDataProvider {
"upsert, and bulk operations", table.getSqlName());
}

#! Returns data provider info
hash<DataProviderInfo> getInfo() {
*hash<string, hash<DataProviderSearchOperatorInfo>> search_operator_info = map {
$1.key: <DataProviderSearchOperatorInfo>{
"name": $1.value.name,
"type": $1.value.type,
"desc": $1.value.desc,
},
}, table.getWhereOperatorMap().pairIterator(), $1.value.name && $1.value.type && $1.value.desc;

hash<DataProviderInfo> rv = AbstractDataProvider::getInfo();
rv.search_operators = search_operator_info;
return rv;
}

#! Returns child providers; return @ref nothing if there are no child providers
*AbstractDataProvider getChildProviders() {
}
Expand Down
1 change: 1 addition & 0 deletions qlib/FixedLengthUtil/FixedLengthReadDataProvider.qc
Expand Up @@ -36,6 +36,7 @@ public class FixedLengthReadDataProvider inherits DataProvider::AbstractDataProv
"has_record": True,
"constructor_options": ConstructorOptions,
"search_options": GenericRecordSearchOptions,
"search_operators": AbstractDataProvider::GenericRecordSearchOperators,
};

#! Constructor options
Expand Down
10 changes: 5 additions & 5 deletions qlib/FreetdsSqlUtil.qm
Expand Up @@ -1552,6 +1552,11 @@ any v = c.name;
return schema + "." + name;
}

#! returns the "where" operator map for this object
hash<auto> getWhereOperatorMap() {
return FreetdsOpMap;
}

private string getFromIntern(string from, *hash qh) {
# grep from clause with optional hints
string hstr = from;
Expand Down Expand Up @@ -1606,11 +1611,6 @@ any v = c.name;
return FreetdsAlignTableOptions;
}

#! returns the "where" operator map for this object
private hash getWhereOperatorMap() {
return FreetdsOpMap;
}

#! returns the column operator map for this object
private hash getColumnOperatorMapImpl() {
return FreetdsCopMap;
Expand Down
10 changes: 5 additions & 5 deletions qlib/OracleSqlUtil.qm
Expand Up @@ -2949,6 +2949,11 @@ auto v = c.name;
return sql;
}

#! returns the "where" operator map for this object
hash<auto> getWhereOperatorMap() {
return OracleOpMap;
}

private *list getCreateMiscSqlImpl(*hash opt, bool cache) {
if (cache)
getColumnsUnlocked();
Expand Down Expand Up @@ -3040,11 +3045,6 @@ auto v = c.name;
return OracleAlignTableOptions;
}

#! returns the "where" operator map for this object
private hash<auto> getWhereOperatorMap() {
return OracleOpMap;
}

#! returns the column operator map for this object
private hash<auto> getColumnOperatorMapImpl() {
return OracleCopMap;
Expand Down
Expand Up @@ -23,7 +23,7 @@
*/

# minimum required Qore version
%requires qore >= 0.9.4
%requires qore >= 1.7
# assume local scope for variables, do not use "$" signs
%new-style
# require type definitions everywhere
Expand Down
11 changes: 0 additions & 11 deletions qlib/SalesforceRestDataProvider/SalesforceRestDataProviderDefs.qc
Expand Up @@ -22,17 +22,6 @@
DEALINGS IN THE SOFTWARE.
*/

# minimum required Qore version
%requires qore >= 0.9.4
# assume local scope for variables, do not use "$" signs
%new-style
# require type definitions everywhere
%require-types
#! strict argument handling
%strict-args
# enable all warnings
%enable-all-warnings

#! contains all public definitions in the SalesforceRestDataProvider module
public namespace SalesforceRestDataProvider {
#! SOQL operator info hash as returned by all @ref soql_op_funcs "operator functions"
Expand Down

0 comments on commit 8676140

Please sign in to comment.