173 instance_data.vault moved up, filter became array and supports multiple field entries

This commit is contained in:
2025-11-20 17:39:02 +03:00
parent 503df018aa
commit 36c4aaef58
7 changed files with 54 additions and 48 deletions
+1 -1
View File
@@ -51,7 +51,7 @@
//variables.framework.docs={}; //variables.framework.docs={};
variables.framework.docs.APIName="svc-api"; variables.framework.docs.APIName="svc-api";
variables.framework.docs.APIVersion="0.172"; /*$ git config --global --unset user.password*/ variables.framework.docs.APIVersion="0.173"; /*$ git config --global --unset user.password*/
variables.framework.globalHeaders = structNew(); variables.framework.globalHeaders = structNew();
variables.framework.globalHeaders["Access-Control-Expose-Headers"] = "Location"; variables.framework.globalHeaders["Access-Control-Expose-Headers"] = "Location";
+5 -3
View File
@@ -2,12 +2,14 @@
<!--- version 3---><!---16:42 16.11.2020---> <!--- version 3---><!---16:42 16.11.2020--->
<!--- version 4---><!---2025-09-06 19:15:50 больше не нужно объявлять параметр как list, достаточно оператора IN---> <!--- version 4---><!---2025-09-06 19:15:50 больше не нужно объявлять параметр как list, достаточно оператора IN--->
<!--- version 5---><!---2025-11-20 ILIKE - потеря совместимости со стандартным SQL (можно вернуться к совместимости через lower, но будут проблемы с индексами)---> <!--- version 5---><!---2025-11-20 ILIKE - потеря совместимости со стандартным SQL (можно вернуться к совместимости через lower, но будут проблемы с индексами)--->
<!--- version 6---><!---2025-11-20 ***** фильтр стал массивом вместо структуры, чтобы поддержать множественные вхождения поля --->
<!--- build query filter string ---> <!--- build query filter string --->
<cfparam name="ATTRIBUTES.filter" type="struct"/> <cfparam name="ATTRIBUTES.filter" type="array"/>
<cfloop collection=#ATTRIBUTES.filter# item="item"> <cfloop array=#ATTRIBUTES.filter# index="fltr"><!--- неинтуитивный синтаксис, в индексе не индекс массива, а значение --->
<cfsilent> <cfsilent>
<cfset fltr=structFind(ATTRIBUTES.filter,item)> <!--- <cfset fltr=structFind(ATTRIBUTES.filter,item)> --->
<cfparam name="fltr.ftype"/> <cfparam name="fltr.ftype"/>
<cfparam name="fltr.field"/> <cfparam name="fltr.field"/>
<cfparam name="fltr.compare" default="EQ"/> <cfparam name="fltr.compare" default="EQ"/>
+36 -34
View File
@@ -5,7 +5,7 @@
<!--- v0.02 2024-10-15 ---> <!--- v0.02 2024-10-15 --->
<!--- v0.03 2024-11-27 ---> <!--- v0.03 2024-11-27 --->
<!--- v0.04 2025-11-20 columns---> <!--- v0.04 2025-11-20 columns--->
<!--- v0.05 2025-11-20 multiple field entries in filter, filter became array instead of structure--->
<cffunction name="empty2null" <cffunction name="empty2null"
access="public" access="public"
@@ -77,51 +77,53 @@
<cffunction name="parseFilterParams" <cffunction name="parseFilterParams"
returntype="struct" returntype="array"
output="true" output="true"
hint="parse and collect filter params, for operators see filter_build"> hint="parse and collect filter params, for operators see filter_build">
<!---e.g. duration=NEQ:2---> <!---e.g. duration=NEQ:2--->
<!---*** add test for multiple values---> <!---*** add test for multiple values--->
<cfargument name="params" type="struct" required="true" /><!--- имена полей и колонок в snake_case ---> <cfargument name="params" type="struct" required="true" /><!--- имена полей и колонок в snake_case --->
<cfset var local={}/><!--- *** ---> <cfset var local = {}/><!--- *** --->
<cfset var out=structNew()/> <cfset var out = []/>
<cfloop collection=#params# index="local.item"> <cfloop collection=#params# index="local.item">
<cfset var urlParamName=snake2camel(lCase(local.item))/> <cfset var urlParamName=snake2camel(lCase(local.item))/>
<cfif structKeyExists(URL,urlParamName)> <cfif structKeyExists(URL,urlParamName)>
<cfset var rawValue=URL[urlParamName]/>
<cfset var operator="EQ"/>
<cfset var value=#rawValue#/>
<cfif listLen(rawValue,":") GT 1>
<cfset var operator=listGetAt(rawValue,1,":")/>
<cfset var value=listGetAt(rawValue,2,":")/>
</cfif>
<cfif !isValid(ARGUMENTS.params[local.item].type,value)>
<cfthrow type="invalidParamValue" message="Filter parameter #urlParamName# value is not a valid #ARGUMENTS.params[local.item].type#"/><!---не выводим значение, чтобы исключить XSS--->
</cfif>
<cfif structKeyExists(ARGUMENTS.params[local.item], "column") AND len(ARGUMENTS.params[local.item].column)>
<cfset var column="#ARGUMENTS.params[local.item].column#"/>
<cfelse>
<cfset var column=local.item/>
</cfif>
<cfif structKeyExists(ARGUMENTS.params[local.item], "prefix") AND len(ARGUMENTS.params[local.item].prefix)> <cfloop list=#URL[urlParamName]# item="local.rawValue"><!--- when parameter occures in URL more than once, its values compose a comma delimited list --->
<cfset var fieldName="#ARGUMENTS.params[local.item].prefix#.#column#"/> <cfset var operator="EQ"/>
<cfelse> <cfset var value=#local.rawValue#/>
<cfset var fieldName=#column#/> <cfif listLen(local.rawValue,":") GT 1>
</cfif> <cfset var operator=listGetAt(local.rawValue,1,":")/>
<cfset var value=listGetAt(local.rawValue,2,":")/>
<cfif structKeyExists(ARGUMENTS.params[local.item], "list") AND ARGUMENTS.params[local.item].list GT 0> </cfif>
<cfset var list=ARGUMENTS.params[local.item].list /> <cfif !isValid(ARGUMENTS.params[local.item].type,value)>
<cfelse> <cfthrow type="invalidParamValue" message="Filter parameter #urlParamName# value is not a valid #ARGUMENTS.params[local.item].type#"/><!---не выводим значение, чтобы исключить XSS--->
<cfset var list=false /> </cfif>
</cfif>
<cfif structKeyExists(ARGUMENTS.params[local.item], "expression") AND len(ARGUMENTS.params[local.item].expression)>
<cfset var rec={field=#fieldName#, val=#value#, ftype=#ARGUMENTS.params[local.item].type#, compare=#operator#, list=#list#}/> <cfset var expr="#ARGUMENTS.params[local.item].expression#"/>
<cfset structInsert(out, local.item, rec, true)/> <cfelse>
<cfset var expr=local.item/>
</cfif>
<cfif structKeyExists(ARGUMENTS.params[local.item], "prefix") AND len(ARGUMENTS.params[local.item].prefix)>
<cfset var fieldName="#ARGUMENTS.params[local.item].prefix#.#expr#"/>
<cfelse>
<cfset var fieldName=#expr#/>
</cfif>
<cfif structKeyExists(ARGUMENTS.params[local.item], "list") AND ARGUMENTS.params[local.item].list GT 0>
<cfset var list=ARGUMENTS.params[local.item].list />
<cfelse>
<cfset var list=false />
</cfif>
<cfset var rec={field=#fieldName#, val=#value#, ftype=#ARGUMENTS.params[local.item].type#, compare=#operator#, list=#list#}/>
<cfset arrayAppend(out, rec)/>
</cfloop>
</cfif> </cfif>
</cfloop> </cfloop>
+1
View File
@@ -116,6 +116,7 @@
<m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field> <m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field>
<m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data->>'out' as out</m:field> <m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data->>'out' as out</m:field>
<m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data->>'params' as params</m:field> <m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data->>'params' as params</m:field>
<m:field formatter=#function(x){return (deserializeJson(x))}#>st.instance_data->>'vault' as vault</m:field>
<m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isDeleted')::boolean as is_deleted</m:field> <m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isDeleted')::boolean as is_deleted</m:field>
<m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isSuspended')::boolean as is_suspended</m:field> <m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isSuspended')::boolean as is_suspended</m:field>
</m:field_set> </m:field_set>
+3 -3
View File
@@ -85,8 +85,8 @@
<cfset local.fieldsShortList=""/> <cfset local.fieldsShortList=""/>
<!--- теперь добавим в перечень полей, которые нужно оставить, поля для фильтрации и сортировки ---> <!--- теперь добавим в перечень полей, которые нужно оставить, поля для фильтрации и сортировки --->
<cfloop collection=#filter# item="item"> <cfloop array=#filter# index="fltr">
<cfset var fltr = structFind(filter,item)/> <!--- <cfset var fltr = structFind(filter,item)/> --->
<cfif listLen(fltr.field,".") EQ 2> <cfif listLen(fltr.field,".") EQ 2>
<cfset var fld = listGetAt(fltr.field,2,".")/> <cfset var fld = listGetAt(fltr.field,2,".")/>
<cfelseif listLen(fltr.field,".") EQ 1> <cfelseif listLen(fltr.field,".") EQ 1>
@@ -202,7 +202,7 @@
from ( from (
select select
<!--- из-за фильтраци по вложенному селекту нам нужны значения колонок ---> <!--- из-за фильтраци по вложенному селекту нам нужны значения колонок --->
<cfif structIsEmpty(filter)> <cfif arrayIsEmpty(filter)>
'' as placeholder '' as placeholder
<cfelse> <cfelse>
#preserveSingleQuotes(definedQueryFields)# #preserveSingleQuotes(definedQueryFields)#
+6 -5
View File
@@ -12,11 +12,11 @@
"dt_created"={prefix="o", type="date"}, "dt_created"={prefix="o", type="date"},
"dt_updated"={prefix="o", type="date"}, "dt_updated"={prefix="o", type="date"},
"creator_id"={prefix="o", type="integer"}, "creator_id"={prefix="o", type="integer"},
"creator_email"={prefix="c", column="email", type="string"}, "creator_email"={prefix="c", expression="email", type="string"},
"creator_shortname"={prefix="c", column="shortname", type="string"}, "creator_shortname"={prefix="c", expression="shortname", type="string"},
"updater_id"={prefix="o", type="integer"}, "updater_id"={prefix="o", type="integer"},
"updater_email"={prefix="u", column="email", type="string"}, "updater_email"={prefix="u", expression="email", type="string"},
"updater_shortname"={prefix="u", column="shortname", type="string"}, "updater_shortname"={prefix="u", expression="shortname", type="string"},
"dt_submit"={prefix="o", type="date"}, "dt_submit"={prefix="o", type="date"},
"submit_result"={prefix="o", type="string"}, "submit_result"={prefix="o", type="string"},
"dt_start"={prefix="o", type="date"}, "dt_start"={prefix="o", type="date"},
@@ -26,7 +26,8 @@
"contract_id"={prefix="inr", type="integer"}, "contract_id"={prefix="inr", type="integer"},
"display_name"={prefix="e", type="string"}, "display_name"={prefix="e", type="string"},
"service_id"={prefix="e", type="integer"}, "service_id"={prefix="e", type="integer"},
"svc"={prefix="v", type="string"} "svc"={prefix="v", type="string"},
"duration"={expression="extract('epoch' from coalesce(o.dt_finish,CURRENT_TIMESTAMP)- o.dt_start)", type="numeric"}
} }
/> />
+2 -2
View File
@@ -65,8 +65,8 @@
<cfset local.fieldsShortList=""/> <cfset local.fieldsShortList=""/>
<!--- теперь добавим в перечень полей, которые нужно оставить, поля для фильтрации и сортировки ---> <!--- теперь добавим в перечень полей, которые нужно оставить, поля для фильтрации и сортировки --->
<cfloop collection=#filter# item="item"> <cfloop array=#filter# index="fltr">
<cfset var fltr = structFind(filter,item)/> <!--- <cfset var fltr = structFind(filter,item)/> --->
<cfif listLen(fltr.field,".") EQ 2> <cfif listLen(fltr.field,".") EQ 2>
<cfset var fld = listGetAt(fltr.field,2,".")/> <cfset var fld = listGetAt(fltr.field,2,".")/>
<cfelseif listLen(fltr.field,".") EQ 1> <cfelseif listLen(fltr.field,".") EQ 1>