diff --git a/lib/sql/connection.p b/lib/sql/connection.p index 00f9316..1863d31 100644 --- a/lib/sql/connection.p +++ b/lib/sql/connection.p @@ -33,8 +33,7 @@ pfClass ^if(!def $self.dialect){ $self.dialect[^switch[$self.serverType]{ ^case[mysql;mysql8;mysql57]{^pfSQLMySQLDialect::create[]} - ^case[pgsql]{^pfSQLPostgresDialect::create[]} - ^case[postgresql]{^pfSQLPostgresDialect::create[]} + ^case[pgsql;postgresql]{^pfSQLPostgresDialect::create[]} ^case[sqlite]{^pfSQLSQLiteDialect::create[]} ^case[DEFAULT]{^pfSQLAnsiDialect::create[]} }] @@ -230,6 +229,14 @@ pfClass $lOptions[^self._getOptions[$lQuery;hash;$aSQLOptions;$aOptions]] $result[^self._processMemoryCache{^self._sql[hash]{^hash::sql{$lQuery}[$aSQLOptions]}[$lOptions]}[$lOptions]] +@array[aQuery;aSQLOptions;aOptions] +## aOptions.force — отключить кеширование в памяти +## aOptions.cacheKey — ключ для кеширования. Если не задан, то вычисляется автоматически. +## aOptions.log[] — запись, которую надо сделать в логе вместо текста запроса. + $lQuery[$aQuery] + $lOptions[^self._getOptions[$lQuery;array;$aSQLOptions;$aOptions]] + $result[^self._processMemoryCache{^self._sql[array]{^array::sql{$lQuery}[$aSQLOptions]}[$lOptions]}[$lOptions]] + @file[aQuery;aSQLOptions;aOptions] ## aOptions.force — отключить кеширование в памяти ## aOptions.cacheKey — ключ для кеширования. Если не задан, то вычисляется автоматически. @@ -353,7 +360,7 @@ pfClass $.results(^switch[$aType]{ ^case[DEFAULT;void]{0} ^case[int;double;string;file]{1} - ^case[table;hash]{^eval($result)} + ^case[table;hash;array]{^eval($result)} }) $.exception[$lException] ] diff --git a/lib/sql/models/sql_table.p b/lib/sql/models/sql_table.p index a7631f1..fc9d534 100644 --- a/lib/sql/models/sql_table.p +++ b/lib/sql/models/sql_table.p @@ -20,6 +20,7 @@ pfClass ## aOptions.schema — название схемы в БД (можно не указывать) ## aOptions.builder ## aOptions.allAsTable(false) — по-умолчанию возвращать результат в виде таблицы +## aOptions.allAsTable(false) — по-умолчанию возвращать результат в виде массива хешей ## aOptions.readOnlyTable(false) — модель только для чтения (методы, менящие данне, вызовут исключение) ## Следующие поля необязательны, но полезны @@ -63,7 +64,7 @@ pfClass $self._skipOnUpdate[^hash::create[^if(def $aOptions.skipOnUpdate){$aOptions.skipOnUpdate}]] $self._skipOnSelect[^hash::create[^if(def $aOptions.skipOnSelect){$aOptions.skipOnSelect}]] - $self._defaultResultType[^if(^aOptions.allAsTable.bool(false)){table}{hash}] + $self._defaultResultType[^if(^aOptions.allAsTable.bool(false)){table}(^aOptions.allAsArray.bool(false)){array}{hash}] $self._defaultOrderBy[] $self._defaultGroupBy[] @@ -330,6 +331,7 @@ pfClass ## aOptions.asTable(false) — возвращаем таблицу ## aOptions.asHash(false) — возвращаем хеш (ключ хеша — первичный ключ таблицы) ## aOptions.asHashOn[fieldName] — возвращаем хеш таблиц, ключем которого будет fieldName +## aOptions.asArray(false) — возвращаем массив ## Выражения для контроля выборки (код в фигурных скобках): ## aOptions.selectFieldsGroups[group1, group2] — выбрать толкьо поля с группами ## aOptions.selectFields{exression} — выражение для списка полей (вместо автогенерации) @@ -401,7 +403,10 @@ pfClass @aggregate[*aConds] ## Выборки с группировкой ## ^aggregate[func(expr) as alias;_fields(field1, field2 as alias2);_fields(*);_groups(group1, group2);table[];conditions hash;sqlOptions] -## aConds.asHashOn[fieldName] — возвращаем хеш таблиц, ключем которого будет fieldName +## aOptions.asTable(false) — возвращаем таблицу +## aOptions.asHash(false) — возвращаем хеш (ключ хеша — первичный ключ таблицы) +## aOptions.asHashOn[fieldName] — возвращаем хеш таблиц, ключем которого будет fieldName +## aOptions.asArray(false) — возвращаем массив $lConds[^self.__getAgrConds[$aConds]] $lResultType[^if(def $lConds.options.asHashOn){table}{^self.__getResultType[$lConds.options]}] $lExpression[^self.__aggregateSQLExpression[$lResultType;$lConds]] @@ -794,6 +799,7 @@ pfClass $result[^switch(true){ ^case(^aOptions.asTable.bool(false)){table} ^case(^aOptions.asHash.bool(false)){hash} + ^case(^aOptions.asArray.bool(false)){array} ^case[DEFAULT]{$self._defaultResultType} }] diff --git a/lib/tests/unittest.p b/lib/tests/unittest.p index 2612d84..536dcf7 100644 --- a/lib/tests/unittest.p +++ b/lib/tests/unittest.p @@ -763,6 +763,8 @@ $aException.file (${aException.lineno}:$aException.colno)] $.bool[math] $.string[string] $.void[string] + $.table[json_string] + $.array[json_string] $.hash[recursive] $._default[fail] ] @@ -793,6 +795,7 @@ $aException.file (${aException.lineno}:$aException.colno)] ^if( ($lExpEqType eq "math" && $aActual.[$k] != $v) || ($lExpEqType eq "string" && $aActual.[$k] ne $v) + || ($lExpEqType eq "json_string" && "^json:string[$aActual.[$k]]" ne "^json:string[$v]") ){ ^throw[$lExceptionType;Not equals value for "$k" key] } diff --git a/ut/sql/sql_connection_test.p b/ut/sql/sql_connection_test.p index 53f8b0d..0bb7020 100644 --- a/ut/sql/sql_connection_test.p +++ b/ut/sql/sql_connection_test.p @@ -167,6 +167,19 @@ BaseTestSQLConnection $.7[$.b[700]] ] +@testArray[] + $lTable[^self.createTestTable[]] + ^self.assertNotRaises[sql.execute]{ + $lActual[^self.sut.array{select * from $lTable order by a}[$.limit(2) $.offset(5)]] + } + ^self.assertEqualsAsJSON[$lActual; + ^array::create[ + $.a[6] $.b[600] + ][ + $.a[7] $.b[700] + ] + ] + @testString[] ^self.assertEq[^self.sut.string{select 'string data'};string data] diff --git a/ut/sql/sql_table/orm_test.p b/ut/sql/sql_table/orm_test.p index 6bbd942..defcd6b 100644 --- a/ut/sql/sql_table/orm_test.p +++ b/ut/sql/sql_table/orm_test.p @@ -31,21 +31,142 @@ BaseTestSQLConnection ] } -@testAll_AsHash[] +@testAll_allAsHash[] ^self._createTestUsers(5) $lRes[^self.sut.all[]] + ^self.assertTrue($lRes is hash)[result is $lRes.CLASS_NAME istead of hash] ^self.assertNumEq($lRes;5) ^self.assertHashEquals[$lRes.1; - $.userID[1] - $.login[user_4] - $.name[name] - $.job[job 1] - $.passwordHash[password hash 1] - $.isAdmin[0] - $.isActive[1] - $.createdAt[2022-07-03 14:52:18] - $.updatedAt[2022-07-04 22:04:18] + $.userID[1] + $.login[user_4] + $.name[name] + $.job[job 1] + $.passwordHash[password hash 1] + $.isAdmin[0] + $.isActive[1] + $.createdAt[2022-07-03 14:52:18] + $.updatedAt[2022-07-04 22:04:18] + ] + +@testAll_allAsTable[] + ^self._createTestUsers(5) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + $.allAsTable(true) + ]] + + $lRes[^self.sut.all[]] + + ^self.assertTrue($lRes is table)[result is $lRes.CLASS_NAME istead of table] + ^self.assertNumEq($lRes;5) + ^self.assertHashEquals[$lRes.fields; + $.userID[1] + $.login[user_4] + $.name[name] + $.job[job 1] + $.passwordHash[password hash 1] + $.isAdmin[0] + $.isActive[1] + $.createdAt[2022-07-03 14:52:18] + $.updatedAt[2022-07-04 22:04:18] + ] + +@testAll_allAsArray[] + ^self._createTestUsers(5) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + $.allAsArray(true) + ]] + + $lRes[^self.sut.all[]] + + ^self.assertTrue($lRes is array)[result is $lRes.CLASS_NAME istead of array] + ^self.assertNumEq($lRes;5) + ^self.assertHashEquals[$lRes.1; + $.userID[2] + $.login[user_3] + $.name[name] + $.job[job 2] + $.passwordHash[password hash 2] + $.isAdmin[0] + $.isActive[1] + $.createdAt[2022-07-03 14:52:18] + $.updatedAt[2022-07-04 22:04:18] + ] + +@testAll_asTableOption[] + ^self._createTestUsers(5) + + $lRes[^self.sut.all[ + $.asTable(true) + ]] + + ^self.assertTrue($lRes is table)[result is $lRes.CLASS_NAME istead of table] + ^self.assertNumEq($lRes;5) + ^self.assertHashEquals[$lRes.fields; + $.userID[1] + $.login[user_4] + $.name[name] + $.job[job 1] + $.passwordHash[password hash 1] + $.isAdmin[0] + $.isActive[1] + $.createdAt[2022-07-03 14:52:18] + $.updatedAt[2022-07-04 22:04:18] + ] + +@testAll_asHashOption[] + ^self._createTestUsers(5) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + $.allAsTable(true) + ]] + + $lRes[^self.sut.all[ + $.asHash(true) + ]] + + ^self.assertTrue($lRes is hash)[result is $lRes.CLASS_NAME istead of hash] + ^self.assertNumEq($lRes;5) + ^self.assertHashEquals[$lRes.1; + $.userID[1] + $.login[user_4] + $.name[name] + $.job[job 1] + $.passwordHash[password hash 1] + $.isAdmin[0] + $.isActive[1] + $.createdAt[2022-07-03 14:52:18] + $.updatedAt[2022-07-04 22:04:18] + ] + +@testAll_asArrayOption[] + ^self._createTestUsers(5) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + ]] + + $lRes[^self.sut.all[ + $.asArray(true) + ]] + + ^self.assertTrue($lRes is array)[result is $lRes.CLASS_NAME istead of array] + ^self.assertNumEq($lRes;5) + ^self.assertHashEquals[$lRes.1; + $.userID[2] + $.login[user_3] + $.name[name] + $.job[job 2] + $.passwordHash[password hash 2] + $.isAdmin[0] + $.isActive[1] + $.createdAt[2022-07-03 14:52:18] + $.updatedAt[2022-07-04 22:04:18] ] @testAll_selectGroups[] @@ -118,6 +239,142 @@ BaseTestSQLConnection ]] } +@testAggregate_allAsTable[] + ^self._createTestUsers(50) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + $.allAsTable(true) + ]] + + $lRes[^self.sut.aggregate[ + count(*) as total; + sum(case when $sut.userID % 5 = 0 then 1 else 0 end) as dividedBy5; + ][ + $.orderBy[] + ]] + + ^self.assertTrue($lRes is table)[result is $lRes.CLASS_NAME istead of table] + ^self.assertNumEq($lRes;1) + ^self.assertHashEquals[$lRes.fields; + $.total[50] + $.dividedBy5[10] + ] + +@testAggregate_allAsHash[] + ^self._createTestUsers(50) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + ]] + + $lRes[^self.sut.aggregate[ + count(*) as total; + sum(case when $sut.userID % 5 = 0 then 1 else 0 end) as dividedBy5; + ][ + $.orderBy[] + ]] + + ^self.assertTrue($lRes is hash)[result is $lRes.CLASS_NAME istead of hash] + ^self.assertNumEq($lRes;1) + ^self.assertHashEquals[$lRes; + $.50[ + $.dividedBy5[10] + ] + ] + +@testAggregate_allAsArray[] + ^self._createTestUsers(50) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + $.allAsArray(true) + ]] + + $lRes[^self.sut.aggregate[ + count(*) as total; + sum(case when $sut.userID % 5 = 0 then 1 else 0 end) as dividedBy5; + ][ + $.orderBy[] + ]] + + ^self.assertTrue($lRes is array)[result is $lRes.CLASS_NAME istead of array] + ^self.assertNumEq($lRes;1) + ^self.assertHashEquals[$lRes.0; + $.total[50] + $.dividedBy5[10] + ] + +@testAggregate_asArrayOption[] + ^self._createTestUsers(50) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + $.allAsArray(false) + ]] + + $lRes[^self.sut.aggregate[ + count(*) as total; + sum(case when $sut.userID % 5 = 0 then 1 else 0 end) as dividedBy5; + ][ + $.asArray(true) + $.orderBy[] + ]] + + ^self.assertTrue($lRes is array)[result is $lRes.CLASS_NAME istead of array] + ^self.assertNumEq($lRes;1) + ^self.assertHashEquals[$lRes.0; + $.total[50] + $.dividedBy5[10] + ] + +@testAggregate_asHashOption[] + ^self._createTestUsers(50) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + ]] + + $lRes[^self.sut.aggregate[ + count(*) as total; + sum(case when $sut.userID % 5 = 0 then 1 else 0 end) as dividedBy5; + ][ + $.asHash(true) + $.orderBy[] + ]] + + ^self.assertTrue($lRes is hash)[result is $lRes.CLASS_NAME istead of hash] + ^self.assertNumEq($lRes;1) + ^self.assertHashEquals[$lRes; + $.50[ + $.dividedBy5[10] + ] + ] + +@testAggregate_asHashOnOption[] + ^self._createTestUsers(50) + + $self.sut[^_TestUserModel::create[ + $.sql[$self.connection] + ]] + + $lRes[^self.sut.aggregate[ + 't1' as key; + count(*) as total; + sum(case when $sut.userID % 5 = 0 then 1 else 0 end) as dividedBy5; + ][ + $.asHashOn[key] + $.orderBy[] + ]] + + ^self.assertTrue($lRes is hash)[result is $lRes.CLASS_NAME istead of hash] + ^self.assertNumEq($lRes;1) + ^self.assertHashEquals[$lRes; + $.t1[^table::create{key,total,dividedBy5 +t1,50,10}[$.separator[,]] + ] + ] + @testOne_skipOrderBy[] ^self._createTestUsers(5) $self.sut._defaultOrderBy[unknown] diff --git a/ut/tests/ut_test_case_test.p b/ut/tests/ut_test_case_test.p index f953a71..b44978d 100644 --- a/ut/tests/ut_test_case_test.p +++ b/ut/tests/ut_test_case_test.p @@ -360,13 +360,19 @@ locals $.f2(25) $.f3(33.1) $.f4[$void] - $.f5[$.f1[v1]] + $.f5[$.f1[v1] $.f2[v1] $.f3[$.0[1] $.1[2]]] + $.f6[^array::create[1;2;3;4;5;6]] + $.f7[^table::create{one,two +1,2}[$.separator[,]]] ][ - $.f1[v1] - $.f2(25) - $.f3(33.10) + $.f7[^table::create{one,two +1,2}[$.separator[,]]] + $.f6[^array::create[1;2;3;4;5;6]] + $.f5[$.f2[v1] $.f3[$.1[2] $.0[1]] $.f1[v1]] $.f4[] - $.f5[$.f1[v1]] + $.f3(33.10) + $.f2(25) + $.f1[v1] ] @testAssertHashEqualsFail[] @@ -415,9 +421,17 @@ locals ^self.assertRaisesFailureException{ ^self.sut.assertHashEquals[ - $.f1[$.f1[v1]] + $.f1[$.f1[v1] $.f2[v2]] + ][ + $.f1[$.f1[v2] $.f2[v2]] + ] + } + + ^self.assertRaisesFailureException{ + ^self.sut.assertHashEquals[ + $.f1[^array::create[1,2,3,4]] ][ - $.f1[$.f1[v2]] + $.f1[^array::create[2,2,3,4]] ] }