pgkiln 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/db/migrations/102_app_computations.sql +345 -0
  2. package/db/migrations/103_shortcuts.sql +341 -0
  3. package/db/migrations/104_report_queries.sql +413 -0
  4. package/db/migrations/105_component_settings_groups.sql +367 -0
  5. package/docs/apex-feature-parity.md +10 -10
  6. package/docs/guide/02-concepts.md +24 -0
  7. package/docs/guide/03-builder.md +33 -1
  8. package/docs/guide/04-pages-and-regions.md +10 -1
  9. package/docs/guide/06-processing.md +22 -0
  10. package/docs/guide/09-reference.md +13 -0
  11. package/docs/guide/12-development.md +2 -1
  12. package/docs/guide/16-files.md +23 -0
  13. package/docs/guide/18-cli.md +4 -0
  14. package/package.json +1 -1
  15. package/public/app.css +17 -0
  16. package/public/app.js +63 -0
  17. package/src/appfiles.ts +18 -2
  18. package/src/builder/advisor.ts +7 -2
  19. package/src/builder/code-editor.ts +6 -1
  20. package/src/builder/components.ts +85 -3
  21. package/src/builder/forms.ts +1 -1
  22. package/src/builder/shared.ts +39 -6
  23. package/src/builder/subscriptions.ts +30 -4
  24. package/src/cli/replace.ts +6 -2
  25. package/src/metadata.ts +14 -1
  26. package/src/runtime/component-settings.ts +42 -0
  27. package/src/runtime/context.ts +4 -0
  28. package/src/runtime/documents.ts +29 -3
  29. package/src/runtime/engine.ts +2 -2
  30. package/src/runtime/grid.ts +22 -4
  31. package/src/runtime/items.ts +14 -11
  32. package/src/runtime/logic.ts +12 -7
  33. package/src/runtime/regions.ts +4 -3
  34. package/src/runtime/render.ts +1 -0
  35. package/src/runtime/report.ts +2 -1
  36. package/src/runtime/routes.ts +4 -2
  37. package/src/runtime/shortcuts.ts +43 -0
  38. package/src/security.ts +2 -1
  39. package/src/subscriptions.ts +67 -0
@@ -0,0 +1,413 @@
1
+ -- =====================================================================
2
+ -- 104: report queries (APEX: Shared Components → Report Queries)
3
+ --
4
+ -- meta.report_query: a named query of an application and how it is
5
+ -- printed: as PDF (with a report layout), Excel, CSV, JSON, XML or HTML.
6
+ -- Any page of the application downloads it with ?doc=NAME (a document
7
+ -- template of that name wins), with the page's session state in the
8
+ -- query's binds; a button with the action "document" names it.
9
+ -- export_app / import_app are redefined from 103 to carry report_queries.
10
+ -- =====================================================================
11
+ create table meta.report_query (
12
+ id serial primary key,
13
+ app_id int not null references meta.app on delete cascade,
14
+ name text not null check (name ~ '^[A-Z][A-Z0-9_]{0,59}$'),
15
+ description text,
16
+ -- a SELECT with :ITEM binds, run as the application's role
17
+ query text not null check (trim(query) <> ''),
18
+ format text not null default 'pdf' check (format in ('pdf', 'xlsx', 'csv', 'json', 'xml', 'html')),
19
+ -- a report layout of the application (PDF); null: the default
20
+ layout text,
21
+ -- the file's name without extension, with &ITEM. substitutions; null: the name in lower case
22
+ file_name text,
23
+ -- the title of a PDF or HTML file, with &ITEM. substitutions; null: the description or the name
24
+ title text,
25
+ -- who may download it (besides access to the page)
26
+ authz text,
27
+ comments text,
28
+ unique (app_id, name)
29
+ );
30
+ revoke all on meta.report_query from public;
31
+ grant select on meta.report_query to pgkiln_runtime;
32
+ comment on table meta.report_query is 'Report queries (104): named queries printed as PDF, Excel, CSV, JSON, XML or HTML (?doc=NAME on a page)';
33
+
34
+ -- (090) an isolated workspace's logins see their own applications' rows only
35
+ alter table meta.report_query enable row level security;
36
+ create policy workspace on meta.report_query
37
+ using ((select meta.isolation_off()) or app_id in (select meta.visible_apps()))
38
+ with check ((select meta.isolation_off()) or app_id in (select meta.visible_apps()));
39
+
40
+ create or replace function meta.export_app(p_alias text) returns jsonb
41
+ language sql stable set search_path = meta, pg_catalog as $$
42
+ select jsonb_build_object(
43
+ 'format', 'pgkiln/2',
44
+ -- (083) availability and build status belong to the installation
45
+ 'app', to_jsonb(a) - 'id' - 'created_at' - 'updated_at' - 'availability' - 'availability_message' - 'availability_users' - 'build_status',
46
+ 'authz_schemes', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.authz_scheme x where x.app_id = a.id), '[]'),
47
+ 'app_items', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.app_item x where x.app_id = a.id), '[]'),
48
+ 'app_processes', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.seq, x.id) from meta.app_process x where x.app_id = a.id), '[]'),
49
+ 'lovs', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.lov x where x.app_id = a.id), '[]'),
50
+ 'group_roles', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.group_name, x.role) from meta.app_group_role x where x.app_id = a.id), '[]'),
51
+ 'text_messages', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.name, x.language) from meta.text_message x where x.app_id = a.id), '[]'),
52
+ 'translations', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.language, x.source) from meta.translation x where x.app_id = a.id), '[]'),
53
+ 'report_layouts', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'logo' || jsonb_build_object('logo', encode(x.logo, 'base64')) order by x.name)
54
+ from meta.report_layout x where x.app_id = a.id), '[]'),
55
+ 'automations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'next_run_at' - 'last_run_at' - 'last_status' - 'code' order by x.name)
56
+ from meta.automation x where x.app_id = a.id), '[]'),
57
+ -- (044) the actions of the automations, by automation name
58
+ 'automation_actions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.automation_name collate "C", x.seq, x.name collate "C")
59
+ from meta.automation_action x where x.app_id = a.id), '[]'),
60
+ 'document_templates', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.document_template x where x.app_id = a.id), '[]'),
61
+ 'task_definitions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.task_definition x where x.app_id = a.id), '[]'),
62
+ 'workflow_definitions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.workflow_definition x where x.app_id = a.id), '[]'),
63
+ 'rest_modules', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.rest_module x where x.app_id = a.id), '[]'),
64
+ 'template_components', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.static_id) from meta.template_component x where x.app_id = a.id), '[]'),
65
+ 'build_options', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.build_option x where x.app_id = a.id), '[]'),
66
+ -- (030) secrets never leave the installation: they are entered again after an import
67
+ -- (050) nor do the password and refresh token of the OAuth2 flows, or the token state
68
+ 'web_credentials', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'secret_enc' - 'password_enc' - 'refresh_token_enc' - 'token_refreshed_at' order by x.name)
69
+ from meta.web_credential x where x.app_id = a.id), '[]'),
70
+ -- (050) the synchronisation's state (next and last run) belongs to the installation
71
+ 'rest_sources', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'sync_next_at' - 'sync_last_at' - 'sync_last_status' order by x.name)
72
+ from meta.rest_source x where x.app_id = a.id), '[]'),
73
+ -- (041) data load definitions
74
+ 'data_load_definitions', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.data_load_def x where x.app_id = a.id), '[]'),
75
+ -- (042) lists; entries keep their ids so parents can be remapped on import
76
+ 'lists', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.list x where x.app_id = a.id), '[]'),
77
+ 'list_entries', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.list_name, x.parent_id nulls first, x.seq, x.id) from meta.list_entry x where x.app_id = a.id), '[]'),
78
+ -- (042) supporting objects: install, upgrade and deinstall scripts (never run on import)
79
+ 'supporting_scripts', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.supporting_script x where x.app_id = a.id), '[]'),
80
+ -- (069) plug-ins (their files are static files, their templates template components)
81
+ 'plugins', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.plugin x where x.app_id = a.id), '[]'),
82
+ -- (080) search configurations
83
+ 'search_configs', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.search_config x where x.app_id = a.id), '[]'),
84
+ -- (081) e-mail templates
85
+ 'email_templates', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.email_template x where x.app_id = a.id), '[]'),
86
+ -- (086) application settings (with their values) and dynamic translations
87
+ 'app_settings', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.app_setting x where x.app_id = a.id), '[]'),
88
+ 'page_groups', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.page_group x where x.app_id = a.id), '[]'),
89
+ -- (104) report queries
90
+ 'report_queries', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.report_query x where x.app_id = a.id), '[]'),
91
+ -- (103) shortcuts
92
+ 'shortcuts', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.name) from meta.shortcut x where x.app_id = a.id), '[]'),
93
+ -- (102) application computations
94
+ 'app_computations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.seq, x.id) from meta.app_computation x where x.app_id = a.id), '[]'),
95
+ 'dynamic_translations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' order by x.language, x.source collate "C") from meta.dynamic_translation x where x.app_id = a.id), '[]'),
96
+ -- (068) static application files (JavaScript, CSS, images, fonts) as base64
97
+ 'static_files', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'app_id' - 'content' - 'updated_at' || jsonb_build_object('content', translate(encode(x.content, 'base64'), E'\n', '')) order by x.name)
98
+ from meta.static_file x where x.app_id = a.id), '[]'),
99
+ -- nav entries and regions keep their ids, so parents and references can be remapped on import
100
+ 'nav', coalesce((select jsonb_agg(to_jsonb(x) - 'app_id' order by x.parent_id nulls first, x.seq, x.id) from meta.nav_entry x where x.app_id = a.id), '[]'),
101
+ 'pages', coalesce((
102
+ select jsonb_agg(to_jsonb(p) - 'id' - 'app_id' || jsonb_build_object(
103
+ 'regions', coalesce((select jsonb_agg(to_jsonb(r) - 'page_id' order by r.seq, r.id) from meta.region r where r.page_id = p.id), '[]'),
104
+ 'items', coalesce((select jsonb_agg(to_jsonb(i) - 'id' - 'page_id' order by i.seq, i.id) from meta.item i where i.page_id = p.id), '[]'),
105
+ 'buttons', coalesce((select jsonb_agg(to_jsonb(b) - 'id' - 'page_id' order by b.seq, b.id) from meta.button b where b.page_id = p.id), '[]'),
106
+ 'dynamic_actions', coalesce((select jsonb_agg(to_jsonb(d) - 'id' - 'page_id' order by d.seq, d.id) from meta.dynamic_action d where d.page_id = p.id), '[]'),
107
+ 'validations', coalesce((select jsonb_agg(to_jsonb(v) - 'id' - 'page_id' order by v.seq, v.id) from meta.validation v where v.page_id = p.id), '[]'),
108
+ 'processes', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'page_id' order by x.seq, x.id) from meta.process x where x.page_id = p.id), '[]'),
109
+ 'computations', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'page_id' order by x.seq, x.id) from meta.computation x where x.page_id = p.id), '[]'),
110
+ 'branches', coalesce((select jsonb_agg(to_jsonb(x) - 'id' - 'page_id' order by x.seq, x.id) from meta.branch x where x.page_id = p.id), '[]')
111
+ ) order by p.page_no)
112
+ from meta.page p where p.app_id = a.id), '[]'))
113
+ from meta.app a
114
+ where a.alias = p_alias
115
+ $$;
116
+
117
+ -- ---------------------------------------------------------------- import
118
+ -- Same as 081, plus app_settings and dynamic_translations.
119
+
120
+ create or replace function meta.import_app(p_doc jsonb, p_alias text default null) returns int
121
+ language plpgsql set search_path = meta, pg_catalog as $$
122
+ declare
123
+ v_app_id int;
124
+ v_page_id int;
125
+ v_page jsonb;
126
+ v_e jsonb;
127
+ v_rmap jsonb;
128
+ v_nmap jsonb := '{}';
129
+ v_lmap jsonb := '{}';
130
+ v_new_id int;
131
+ begin
132
+ if p_doc->>'format' is distinct from 'pgkiln/2' then
133
+ raise exception 'unsupported export format %', coalesce(p_doc->>'format', '(none)');
134
+ end if;
135
+
136
+ insert into meta.app
137
+ select (jsonb_populate_record(null::meta.app, p_doc->'app' || jsonb_build_object(
138
+ 'id', nextval('meta.app_id_seq'),
139
+ 'alias', coalesce(p_alias, p_doc->'app'->>'alias'),
140
+ 'created_at', now(), 'updated_at', now()))).*
141
+ returning id into v_app_id;
142
+
143
+ insert into meta.authz_scheme
144
+ select (jsonb_populate_record(null::meta.authz_scheme, e || jsonb_build_object('id', nextval('meta.authz_scheme_id_seq'), 'app_id', v_app_id))).*
145
+ from jsonb_array_elements(coalesce(p_doc->'authz_schemes', '[]')) e;
146
+ insert into meta.app_item
147
+ select (jsonb_populate_record(null::meta.app_item, e || jsonb_build_object('id', nextval('meta.app_item_id_seq'), 'app_id', v_app_id))).*
148
+ from jsonb_array_elements(coalesce(p_doc->'app_items', '[]')) e;
149
+ insert into meta.app_process
150
+ select (jsonb_populate_record(null::meta.app_process, e || jsonb_build_object('id', nextval('meta.app_process_id_seq'), 'app_id', v_app_id))).*
151
+ from jsonb_array_elements(coalesce(p_doc->'app_processes', '[]')) e;
152
+ insert into meta.lov
153
+ select (jsonb_populate_record(null::meta.lov, e || jsonb_build_object('id', nextval('meta.lov_id_seq'), 'app_id', v_app_id))).*
154
+ from jsonb_array_elements(coalesce(p_doc->'lovs', '[]')) e;
155
+ insert into meta.app_group_role
156
+ select (jsonb_populate_record(null::meta.app_group_role, e || jsonb_build_object('app_id', v_app_id))).*
157
+ from jsonb_array_elements(coalesce(p_doc->'group_roles', '[]')) e;
158
+ insert into meta.text_message
159
+ select (jsonb_populate_record(null::meta.text_message, e || jsonb_build_object('app_id', v_app_id))).*
160
+ from jsonb_array_elements(coalesce(p_doc->'text_messages', '[]')) e;
161
+ insert into meta.translation
162
+ select (jsonb_populate_record(null::meta.translation, e || jsonb_build_object('app_id', v_app_id))).*
163
+ from jsonb_array_elements(coalesce(p_doc->'translations', '[]')) e;
164
+ insert into meta.report_layout
165
+ select (jsonb_populate_record(null::meta.report_layout, (e - 'logo') || jsonb_build_object(
166
+ 'id', nextval('meta.report_layout_id_seq'), 'app_id', v_app_id,
167
+ 'logo', null))).*
168
+ from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e;
169
+ update meta.report_layout l
170
+ set logo = decode(e->>'logo', 'base64')
171
+ from jsonb_array_elements(coalesce(p_doc->'report_layouts', '[]')) e
172
+ where l.app_id = v_app_id and l.name = e->>'name' and e->>'logo' is not null;
173
+ -- imported automations start switched off: the copy must not run the original's jobs unasked.
174
+ -- (044) a file with actions: the automations' code is ignored; an older file: code becomes the single action
175
+ insert into meta.automation
176
+ select (jsonb_populate_record(null::meta.automation, '{"error_handling": "stop"}'::jsonb
177
+ || jsonb_strip_nulls(case when p_doc ? 'automation_actions' then e - 'code' else e end)
178
+ || jsonb_build_object('id', nextval('meta.automation_id_seq'), 'app_id', v_app_id, 'enabled', false))).*
179
+ from jsonb_array_elements(coalesce(p_doc->'automations', '[]')) e;
180
+ insert into meta.automation_action
181
+ select (jsonb_populate_record(null::meta.automation_action, '{"seq": 10}'::jsonb || jsonb_strip_nulls(e)
182
+ || jsonb_build_object('id', nextval('meta.automation_action_id_seq'), 'app_id', v_app_id))).*
183
+ from jsonb_array_elements(coalesce(p_doc->'automation_actions', '[]')) e;
184
+ insert into meta.document_template
185
+ select (jsonb_populate_record(null::meta.document_template, e || jsonb_build_object('id', nextval('meta.document_template_id_seq'), 'app_id', v_app_id))).*
186
+ from jsonb_array_elements(coalesce(p_doc->'document_templates', '[]')) e;
187
+ insert into meta.task_definition
188
+ select (jsonb_populate_record(null::meta.task_definition, e || jsonb_build_object('id', nextval('meta.task_definition_id_seq'), 'app_id', v_app_id))).*
189
+ from jsonb_array_elements(coalesce(p_doc->'task_definitions', '[]')) e;
190
+ insert into meta.workflow_definition
191
+ select (jsonb_populate_record(null::meta.workflow_definition, e || jsonb_build_object('id', nextval('meta.workflow_definition_id_seq'), 'app_id', v_app_id))).*
192
+ from jsonb_array_elements(coalesce(p_doc->'workflow_definitions', '[]')) e;
193
+ insert into meta.rest_module
194
+ select (jsonb_populate_record(null::meta.rest_module, e || jsonb_build_object('id', nextval('meta.rest_module_id_seq'), 'app_id', v_app_id))).*
195
+ from jsonb_array_elements(coalesce(p_doc->'rest_modules', '[]')) e;
196
+ -- (028) template components: regions and report columns refer to them by static id
197
+ insert into meta.template_component
198
+ select (jsonb_populate_record(null::meta.template_component, e || jsonb_build_object('id', nextval('meta.template_component_id_seq'), 'app_id', v_app_id))).*
199
+ from jsonb_array_elements(coalesce(p_doc->'template_components', '[]')) e;
200
+ -- (029) build options (components name them by name)
201
+ insert into meta.build_option
202
+ select (jsonb_populate_record(null::meta.build_option, e || jsonb_build_object('id', nextval('meta.build_option_id_seq'), 'app_id', v_app_id))).*
203
+ from jsonb_array_elements(coalesce(p_doc->'build_options', '[]')) e;
204
+ -- (030) web credentials arrive without a secret, whatever the document holds
205
+ -- (050) nor a password or refresh token
206
+ insert into meta.web_credential
207
+ select (jsonb_populate_record(null::meta.web_credential, '{"type": "basic", "valid_for": [], "grant_type": "client_credentials"}'::jsonb
208
+ || jsonb_strip_nulls(e - 'secret_enc' - 'password_enc' - 'refresh_token_enc' - 'token_refreshed_at')
209
+ || jsonb_build_object('id', nextval('meta.web_credential_id_seq'), 'app_id', v_app_id))).*
210
+ from jsonb_array_elements(coalesce(p_doc->'web_credentials', '[]')) e;
211
+ -- (050) write-back operations and synchronisation; an imported synchronisation starts switched off
212
+ insert into meta.rest_source
213
+ select (jsonb_populate_record(null::meta.rest_source, '{"method": "GET", "headers": {}, "params": [], "columns": [], "cache_seconds": 0, "timeout_s": 10, "max_rows": 1000,
214
+ "operations": {}, "key_columns": [], "sync_mode": "merge", "sync_delete": false, "sync_time_zone": "UTC"}'::jsonb
215
+ || jsonb_strip_nulls(e - 'sync_next_at' - 'sync_last_at' - 'sync_last_status')
216
+ || jsonb_build_object('id', nextval('meta.rest_source_id_seq'), 'app_id', v_app_id, 'sync_enabled', false))).*
217
+ from jsonb_array_elements(coalesce(p_doc->'rest_sources', '[]')) e;
218
+
219
+ -- (041) data load definitions (the data_load process names them)
220
+ insert into meta.data_load_def
221
+ select (jsonb_populate_record(null::meta.data_load_def, '{"format": "auto", "headers": true, "mode": "append", "skip_errors": false, "columns": []}'::jsonb
222
+ || jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.data_load_def_id_seq'), 'app_id', v_app_id))).*
223
+ from jsonb_array_elements(coalesce(p_doc->'data_load_definitions', '[]')) e;
224
+
225
+ -- (042) lists and their entries, supporting scripts. Entries are inserted
226
+ -- without a parent first and the parents set afterwards, so the order of
227
+ -- the entries in the document doesn't matter (a moved entry may have a
228
+ -- higher id than its children).
229
+ insert into meta.list
230
+ select (jsonb_populate_record(null::meta.list, '{"type": "static"}'::jsonb || jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.list_id_seq'), 'app_id', v_app_id))).*
231
+ from jsonb_array_elements(coalesce(p_doc->'lists', '[]')) e;
232
+ for v_e in select * from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) loop
233
+ insert into meta.list_entry
234
+ select (jsonb_populate_record(null::meta.list_entry, '{"target_items": {}}'::jsonb || jsonb_strip_nulls(v_e) || jsonb_build_object(
235
+ 'id', nextval('meta.list_entry_id_seq'), 'app_id', v_app_id, 'parent_id', null))).*
236
+ returning id into v_new_id;
237
+ if v_e->>'id' is not null then
238
+ v_lmap := v_lmap || jsonb_build_object(v_e->>'id', v_new_id);
239
+ end if;
240
+ end loop;
241
+ update meta.list_entry x
242
+ set parent_id = (v_lmap->>(e->>'parent_id'))::int
243
+ from jsonb_array_elements(coalesce(p_doc->'list_entries', '[]')) e
244
+ where e->>'parent_id' is not null and v_lmap ? (e->>'parent_id') and v_lmap ? (e->>'id')
245
+ and x.id = (v_lmap->>(e->>'id'))::int;
246
+ insert into meta.supporting_script
247
+ select (jsonb_populate_record(null::meta.supporting_script, '{"kind": "install", "seq": 10, "script": ""}'::jsonb || jsonb_strip_nulls(e)
248
+ || jsonb_build_object('id', nextval('meta.supporting_script_id_seq'), 'app_id', v_app_id))).*
249
+ from jsonb_array_elements(coalesce(p_doc->'supporting_scripts', '[]')) e;
250
+
251
+ -- (069) plug-ins
252
+ insert into meta.plugin
253
+ select (jsonb_populate_record(null::meta.plugin, e || jsonb_build_object('id', nextval('meta.plugin_id_seq'), 'app_id', v_app_id))).*
254
+ from jsonb_array_elements(coalesce(p_doc->'plugins', '[]')) e;
255
+
256
+ -- (080) search configurations (search regions name them)
257
+ insert into meta.search_config
258
+ select (jsonb_populate_record(null::meta.search_config, '{"seq": 10, "source_type": "table", "searchable_columns": [], "max_results": 10}'::jsonb
259
+ || jsonb_strip_nulls(e) || jsonb_build_object('id', nextval('meta.search_config_id_seq'), 'app_id', v_app_id))).*
260
+ from jsonb_array_elements(coalesce(p_doc->'search_configs', '[]')) e;
261
+
262
+ -- (081) e-mail templates (meta.send_mail_template and the send_email process name them)
263
+ insert into meta.email_template
264
+ select (jsonb_populate_record(null::meta.email_template, e || jsonb_build_object('id', nextval('meta.email_template_id_seq'), 'app_id', v_app_id))).*
265
+ from jsonb_array_elements(coalesce(p_doc->'email_templates', '[]')) e;
266
+
267
+ -- (104) report queries
268
+ insert into meta.report_query
269
+ select (jsonb_populate_record(null::meta.report_query, '{"format": "pdf"}'::jsonb || jsonb_strip_nulls(e)
270
+ || jsonb_build_object('id', nextval('meta.report_query_id_seq'), 'app_id', v_app_id))).*
271
+ from jsonb_array_elements(coalesce(p_doc->'report_queries', '[]')) e;
272
+
273
+ -- (103) shortcuts
274
+ insert into meta.shortcut
275
+ select (jsonb_populate_record(null::meta.shortcut, '{"type": "text"}'::jsonb || jsonb_strip_nulls(e)
276
+ || jsonb_build_object('id', nextval('meta.shortcut_id_seq'), 'app_id', v_app_id))).*
277
+ from jsonb_array_elements(coalesce(p_doc->'shortcuts', '[]')) e;
278
+
279
+ -- (102) application computations
280
+ insert into meta.app_computation
281
+ select (jsonb_populate_record(null::meta.app_computation, '{"seq": 10, "point": "before_header", "type": "static"}'::jsonb || jsonb_strip_nulls(e)
282
+ || jsonb_build_object('id', nextval('meta.app_computation_id_seq'), 'app_id', v_app_id))).*
283
+ from jsonb_array_elements(coalesce(p_doc->'app_computations', '[]')) e;
284
+
285
+ -- (089) page groups
286
+ insert into meta.page_group
287
+ select (jsonb_populate_record(null::meta.page_group, e || jsonb_build_object('id', nextval('meta.page_group_id_seq'), 'app_id', v_app_id))).*
288
+ from jsonb_array_elements(coalesce(p_doc->'page_groups', '[]')) e;
289
+
290
+ -- (086) application settings and dynamic translations
291
+ insert into meta.app_setting
292
+ select (jsonb_populate_record(null::meta.app_setting, e || jsonb_build_object('id', nextval('meta.app_setting_id_seq'), 'app_id', v_app_id))).*
293
+ from jsonb_array_elements(coalesce(p_doc->'app_settings', '[]')) e;
294
+ insert into meta.dynamic_translation
295
+ select (jsonb_populate_record(null::meta.dynamic_translation, e || jsonb_build_object('id', nextval('meta.dynamic_translation_id_seq'), 'app_id', v_app_id))).*
296
+ from jsonb_array_elements(coalesce(p_doc->'dynamic_translations', '[]')) e;
297
+
298
+ -- (068) static application files
299
+ insert into meta.static_file (app_id, name, mime, content, updated_at)
300
+ select v_app_id, e->>'name', e->>'mime', decode(coalesce(e->>'content', ''), 'base64'), now()
301
+ from jsonb_array_elements(coalesce(p_doc->'static_files', '[]')) e;
302
+
303
+ for v_e in select * from jsonb_array_elements(coalesce(p_doc->'nav', '[]')) loop
304
+ insert into meta.nav_entry
305
+ select (jsonb_populate_record(null::meta.nav_entry, v_e || jsonb_build_object(
306
+ 'id', nextval('meta.nav_entry_id_seq'), 'app_id', v_app_id,
307
+ 'parent_id', v_nmap->>(v_e->>'parent_id')))).*
308
+ returning id into v_new_id;
309
+ v_nmap := v_nmap || jsonb_build_object(v_e->>'id', v_new_id);
310
+ end loop;
311
+
312
+ for v_page in select * from jsonb_array_elements(coalesce(p_doc->'pages', '[]')) loop
313
+ insert into meta.page
314
+ select (jsonb_populate_record(null::meta.page, v_page || jsonb_build_object('id', nextval('meta.page_id_seq'), 'app_id', v_app_id))).*
315
+ returning id into v_page_id;
316
+
317
+ v_rmap := '{}';
318
+ for v_e in select * from jsonb_array_elements(coalesce(v_page->'regions', '[]')) loop
319
+ insert into meta.region
320
+ select (jsonb_populate_record(null::meta.region, v_e || jsonb_build_object('id', nextval('meta.region_id_seq'), 'page_id', v_page_id))).*
321
+ returning id into v_new_id;
322
+ v_rmap := v_rmap || jsonb_build_object(v_e->>'id', v_new_id);
323
+ end loop;
324
+ -- facet and map regions point at their report region by id
325
+ update meta.region r
326
+ set config = jsonb_set(r.config, '{report}', to_jsonb((v_rmap->>(r.config->>'report'))::int))
327
+ where r.page_id = v_page_id and r.type in ('facets', 'smart_filters', 'map') and v_rmap ? (r.config->>'report');
328
+
329
+ insert into meta.item
330
+ select (jsonb_populate_record(null::meta.item, e || jsonb_build_object('id', nextval('meta.item_id_seq'), 'page_id', v_page_id, 'region_id', v_rmap->>(e->>'region_id')))).*
331
+ from jsonb_array_elements(coalesce(v_page->'items', '[]')) e;
332
+ insert into meta.button
333
+ select (jsonb_populate_record(null::meta.button, e || jsonb_build_object('id', nextval('meta.button_id_seq'), 'page_id', v_page_id, 'region_id', v_rmap->>(e->>'region_id')))).*
334
+ from jsonb_array_elements(coalesce(v_page->'buttons', '[]')) e;
335
+ insert into meta.dynamic_action
336
+ select (jsonb_populate_record(null::meta.dynamic_action, e || jsonb_build_object('id', nextval('meta.dynamic_action_id_seq'), 'page_id', v_page_id, 'affected_region_id', v_rmap->>(e->>'affected_region_id')))).*
337
+ from jsonb_array_elements(coalesce(v_page->'dynamic_actions', '[]')) e;
338
+ insert into meta.validation
339
+ select (jsonb_populate_record(null::meta.validation, e || jsonb_build_object('id', nextval('meta.validation_id_seq'), 'page_id', v_page_id))).*
340
+ from jsonb_array_elements(coalesce(v_page->'validations', '[]')) e;
341
+ insert into meta.process
342
+ select (jsonb_populate_record(null::meta.process, e || jsonb_build_object('id', nextval('meta.process_id_seq'), 'page_id', v_page_id, 'region_id', v_rmap->>(e->>'region_id')))).*
343
+ from jsonb_array_elements(coalesce(v_page->'processes', '[]')) e;
344
+ insert into meta.computation
345
+ select (jsonb_populate_record(null::meta.computation, e || jsonb_build_object('id', nextval('meta.computation_id_seq'), 'page_id', v_page_id))).*
346
+ from jsonb_array_elements(coalesce(v_page->'computations', '[]')) e;
347
+ insert into meta.branch
348
+ select (jsonb_populate_record(null::meta.branch, e || jsonb_build_object('id', nextval('meta.branch_id_seq'), 'page_id', v_page_id))).*
349
+ from jsonb_array_elements(coalesce(v_page->'branches', '[]')) e;
350
+ end loop;
351
+
352
+ return v_app_id;
353
+ end
354
+ $$;
355
+
356
+ -- (104) meta.data_export (086) read the rows through the aliases q and x: a query with a column
357
+ -- named q or x broke it ("function row_to_json(text) does not exist"). Redefined with aliases no
358
+ -- column has and whole-row references.
359
+ create or replace function meta.data_export(p_query text, p_format text default 'csv', p_options jsonb default '{}') returns bytea
360
+ language plpgsql volatile as $$
361
+ declare
362
+ v_format text := lower(coalesce(nullif(btrim(p_format), ''), 'csv'));
363
+ v_opts jsonb := coalesce(p_options, '{}');
364
+ v_query text := regexp_replace(coalesce(p_query, ''), '[\s;]+$', '');
365
+ v_sep text := coalesce(v_opts->>'separator', ',');
366
+ v_header boolean := coalesce((v_opts->>'header')::boolean, true);
367
+ v_cols text[];
368
+ v_rows text;
369
+ v_title text := v_opts->>'title';
370
+ begin
371
+ if btrim(v_query) = '' then
372
+ raise exception 'meta.data_export: a query is required';
373
+ end if;
374
+ if v_format not in ('csv', 'json', 'xml', 'html') then
375
+ raise exception 'meta.data_export: the format is csv, json, xml or html (Excel and PDF: the download process)';
376
+ end if;
377
+ if length(v_sep) <> 1 or v_sep ~ '["\r\n]' then
378
+ raise exception 'meta.data_export: the separator is one character';
379
+ end if;
380
+ if v_format = 'json' then
381
+ execute format(E'select coalesce(json_agg("__q".*), ''[]'')::text from (\n%s\n) "__q"', v_query) into v_rows;
382
+ return convert_to(v_rows, 'UTF8');
383
+ end if;
384
+ if v_format = 'xml' then
385
+ return convert_to(query_to_xml(v_query, true, false, '')::text, 'UTF8');
386
+ end if;
387
+ -- the column names, in order, also when there are no rows (the outer join on false reads no row)
388
+ execute format(E'select array(select json_object_keys(row_to_json("__x".*))) from (select "__q".* from (select 1) d left join (\n%s\n) "__q" on false) "__x"', v_query)
389
+ into v_cols;
390
+ if v_format = 'csv' then
391
+ execute format(E'select string_agg((select string_agg(meta.csv_field(e.value #>> ''{}'', %L, json_typeof(e.value) = ''string''), %L order by e.n) from json_each(row_to_json("__q".*)) with ordinality e(k, value, n)), E''\\r\\n'') from (\n%s\n) "__q"',
392
+ v_sep, v_sep, v_query)
393
+ into v_rows;
394
+ return convert_to(
395
+ case when coalesce((v_opts->>'bom')::boolean, true) then chr(65279) else '' end
396
+ || case when v_header then (select string_agg(meta.csv_field(c, v_sep), v_sep order by n) from unnest(v_cols) with ordinality u(c, n)) || E'\r\n' else '' end
397
+ || coalesce(v_rows || E'\r\n', ''),
398
+ 'UTF8');
399
+ end if;
400
+ -- html
401
+ execute format(E'select string_agg(''<tr>'' || (select string_agg(''<td>'' || coalesce(meta.html_escape(e.value), '''') || ''</td>'', '''' order by e.n) from json_each_text(row_to_json("__q".*)) with ordinality e(k, value, n)) || ''</tr>'', E''\\n'') from (\n%s\n) "__q"',
402
+ v_query)
403
+ into v_rows;
404
+ return convert_to(
405
+ '<!doctype html>' || E'\n' || '<html><head><meta charset="utf-8"><title>' || coalesce(meta.html_escape(v_title), 'Export') || '</title></head><body>' || E'\n'
406
+ || case when v_title is not null then '<h1>' || meta.html_escape(v_title) || '</h1>' || E'\n' else '' end
407
+ || '<table>' || E'\n'
408
+ || case when v_header then '<thead><tr>' || coalesce((select string_agg('<th>' || meta.html_escape(c) || '</th>', '' order by n) from unnest(v_cols) with ordinality u(c, n)), '') || '</tr></thead>' || E'\n' else '' end
409
+ || '<tbody>' || E'\n' || coalesce(v_rows || E'\n', '') || '</tbody></table>' || E'\n' || '</body></html>' || E'\n',
410
+ 'UTF8');
411
+ end
412
+ $$;
413
+ grant execute on function meta.data_export(text, text, jsonb) to public;