pgkiln 0.69.0 → 0.71.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.
- package/db/migrations/136_vector_search.sql +252 -0
- package/db/migrations/137_create_page_types.sql +904 -0
- package/db/migrations/138_app_utilities.sql +153 -0
- package/docs/apex-feature-parity.md +10 -8
- package/docs/guide/03-builder.md +176 -4
- package/docs/guide/04-pages-and-regions.md +45 -3
- package/docs/guide/05-items.md +1 -1
- package/docs/guide/06-processing.md +18 -0
- package/docs/guide/09-reference.md +34 -1
- package/docs/guide/12-development.md +53 -3
- package/docs/guide/15-extensions.md +1 -1
- package/docs/guide/16-files.md +1 -1
- package/examples/hr/README.md +1 -0
- package/examples/hr/hr_72_vector_search.sql +158 -0
- package/examples/hr/hr_73_configuration_options.sql +17 -0
- package/examples/hr/hr_74_app_utilities.sql +22 -0
- package/package.json +1 -1
- package/public/app.css +9 -0
- package/public/builder.css +48 -0
- package/src/ai/embeddings.ts +197 -0
- package/src/ai/service.ts +13 -9
- package/src/app.ts +13 -0
- package/src/builder/advisor.ts +43 -3
- package/src/builder/ai-agents.ts +13 -5
- package/src/builder/app-updates.ts +3 -0
- package/src/builder/components.ts +24 -4
- package/src/builder/create-page.ts +259 -0
- package/src/builder/dependencies.ts +311 -0
- package/src/builder/features.ts +313 -0
- package/src/builder/home.ts +3 -0
- package/src/builder/i18n/de.ts +38 -0
- package/src/builder/i18n/en.ts +38 -0
- package/src/builder/i18n/es.ts +38 -0
- package/src/builder/i18n/fr.ts +38 -0
- package/src/builder/i18n/it.ts +38 -0
- package/src/builder/i18n/ja.ts +38 -0
- package/src/builder/i18n/ko.ts +38 -0
- package/src/builder/i18n/nl.ts +38 -0
- package/src/builder/i18n/pt.ts +38 -0
- package/src/builder/i18n/zh-cn.ts +38 -0
- package/src/builder/i18n/zh-tw.ts +38 -0
- package/src/builder/login-page.ts +17 -14
- package/src/builder/newapp.ts +41 -23
- package/src/builder/object-actions.ts +1 -0
- package/src/builder/routes.ts +13 -2
- package/src/builder/shared.ts +1 -1
- package/src/builder/sql.ts +3 -0
- package/src/builder/table-editor.ts +1 -0
- package/src/builder/ui-defaults.ts +161 -0
- package/src/builder/ui.ts +2 -1
- package/src/builder/upgrade.ts +154 -0
- package/src/builder/utilities.ts +181 -0
- package/src/builder/vector-column.ts +229 -0
- package/src/builder/vector-providers.ts +286 -0
- package/src/builder/wizards.ts +211 -26
- package/src/cli/replace.ts +11 -1
- package/src/history.ts +155 -0
- package/src/runtime/ai-agents.ts +53 -2
- package/src/runtime/builtin-components.ts +23 -2
- package/src/runtime/items.ts +4 -2
- package/src/runtime/search.ts +116 -12
- package/src/server.ts +3 -0
- package/src/vector.ts +288 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- 136: vector providers, vector and hybrid search configurations,
|
|
3
|
+
-- vector columns kept current, Retrieve tools (sprint 136, AP-1;
|
|
4
|
+
-- APEX: Vector Providers, APEX_AI.GET_VECTOR_EMBEDDINGS, a search
|
|
5
|
+
-- configuration of type "Oracle Vector Search")
|
|
6
|
+
--
|
|
7
|
+
-- Everything here installs without pgvector: no meta table or function
|
|
8
|
+
-- uses the `vector` type. Vectors cross meta.* as real[] (pgvector casts
|
|
9
|
+
-- real[] to vector and back); the DDL that adds a vector column to an
|
|
10
|
+
-- application's table is written by the builder (SQL Workshop › Table
|
|
11
|
+
-- Properties › Add vector column) only when the extension is installed.
|
|
12
|
+
--
|
|
13
|
+
-- meta.app.vector_providers: the application's vector providers, a JSON
|
|
14
|
+
-- list (exported with the application in app.json, like ai_agents, 121):
|
|
15
|
+
-- {"name": "POLICY_EMBEDDINGS", "description": "…",
|
|
16
|
+
-- "type": "ai_service", "service": "EMBEDDINGS",
|
|
17
|
+
-- "model": "text-embedding-3-small", "dimensions": 1536,
|
|
18
|
+
-- "batch_size": 64, "per_minute": 60}
|
|
19
|
+
-- {"name": "TOY", "type": "function", "function": "sales.embed", "dimensions": 8}
|
|
20
|
+
-- ai_service: an AI service of the installation (OpenAI, Gemini or an
|
|
21
|
+
-- OpenAI-compatible endpoint such as Ollama) and an embedding model; its
|
|
22
|
+
-- key stays in meta.ai_service (encrypted, never exported). function: a
|
|
23
|
+
-- SQL / PL/pgSQL function (text) returning vector, real[] or float8[].
|
|
24
|
+
-- The server checks the list (src/ai/embeddings.ts).
|
|
25
|
+
--
|
|
26
|
+
-- meta.vector_provider(name, app) returns one definition (none are secret).
|
|
27
|
+
-- meta.embed(text, provider, app) returns the embedding as real[] for a
|
|
28
|
+
-- function provider, called as the caller's role (security invoker). An
|
|
29
|
+
-- AI service is called by the pgkiln server, not from SQL: meta.embed
|
|
30
|
+
-- says so for such a provider (vector search, Retrieve tools and the
|
|
31
|
+
-- vector column queue use it).
|
|
32
|
+
--
|
|
33
|
+
-- meta.search_config: search types `vector` (nearest rows by a vector
|
|
34
|
+
-- column, the user's text embedded by vector_provider; distance cosine,
|
|
35
|
+
-- l2 or inner_product; at least min_similarity) and `hybrid` (full-text
|
|
36
|
+
-- and vector rankings merged by reciprocal rank fusion, rows matched by
|
|
37
|
+
-- key_column). The new columns travel with the configuration
|
|
38
|
+
-- (to_jsonb of the row); the defaults trigger fills them for older exports.
|
|
39
|
+
--
|
|
40
|
+
-- Keeping a vector column current (triggers the builder creates on the
|
|
41
|
+
-- application's table; their arguments: app id, provider, vector column,
|
|
42
|
+
-- key column, source columns…):
|
|
43
|
+
-- meta.embedding_set() BEFORE INSERT OR UPDATE OF the source columns:
|
|
44
|
+
-- a function provider's embedding, at once;
|
|
45
|
+
-- meta.embedding_enqueue() AFTER INSERT OR UPDATE OF the source columns:
|
|
46
|
+
-- the row's key into meta.embedding_queue; the
|
|
47
|
+
-- server embeds queued rows in batches, within
|
|
48
|
+
-- the provider's requests per minute and the
|
|
49
|
+
-- application's daily AI limits (src/embeddings.ts).
|
|
50
|
+
-- meta.embedding_queue: installation data, not exported (like
|
|
51
|
+
-- meta.process_job); only the owner reads it.
|
|
52
|
+
-- =====================================================================
|
|
53
|
+
|
|
54
|
+
alter table meta.app add column vector_providers jsonb not null default '[]'
|
|
55
|
+
check (jsonb_typeof(vector_providers) = 'array');
|
|
56
|
+
comment on column meta.app.vector_providers is 'Vector providers (136): an AI service with an embedding model, or a function returning vector; exported with the application';
|
|
57
|
+
|
|
58
|
+
-- an export made before 136 has no vector_providers (nor one before 121 ai_tools / ai_agents)
|
|
59
|
+
create or replace function meta.app_ai_default() returns trigger
|
|
60
|
+
language plpgsql set search_path = pg_catalog as $$
|
|
61
|
+
begin
|
|
62
|
+
new.ai_tools := coalesce(new.ai_tools, '[]');
|
|
63
|
+
new.ai_agents := coalesce(new.ai_agents, '[]');
|
|
64
|
+
new.vector_providers := coalesce(new.vector_providers, '[]');
|
|
65
|
+
return new;
|
|
66
|
+
end $$;
|
|
67
|
+
|
|
68
|
+
-- ---------------------------------------------------------------- providers and meta.embed
|
|
69
|
+
|
|
70
|
+
-- One vector provider of an application (the current one, or one the login may see).
|
|
71
|
+
create function meta.vector_provider(p_name text, p_app int default null) returns jsonb
|
|
72
|
+
language plpgsql stable security definer set search_path = meta, pg_catalog as $$
|
|
73
|
+
declare
|
|
74
|
+
v_app int := coalesce(p_app, meta.app_id());
|
|
75
|
+
begin
|
|
76
|
+
if v_app is null then
|
|
77
|
+
raise exception 'meta.vector_provider: no current application (pass the application''s id)';
|
|
78
|
+
end if;
|
|
79
|
+
if not meta.app_visible(v_app) then
|
|
80
|
+
raise exception 'Application % is not in this workspace.', v_app using errcode = '42501';
|
|
81
|
+
end if;
|
|
82
|
+
return (select e from meta.app a, jsonb_array_elements(a.vector_providers) e
|
|
83
|
+
where a.id = v_app and e->>'name' = upper(p_name) limit 1);
|
|
84
|
+
end
|
|
85
|
+
$$;
|
|
86
|
+
|
|
87
|
+
-- The embedding of a text by a function provider, as real[] (cast it: meta.embed(…)::vector).
|
|
88
|
+
-- Runs the provider's function as the caller (security invoker): its grants apply. Stable, so
|
|
89
|
+
-- "order by embedding <=> meta.embed(…)::vector" embeds the text once (an index scan's argument).
|
|
90
|
+
create function meta.embed(p_text text, p_provider text, p_app int default null) returns real[]
|
|
91
|
+
language plpgsql stable set search_path = meta, pg_catalog as $$
|
|
92
|
+
declare
|
|
93
|
+
v jsonb := meta.vector_provider(p_provider, p_app);
|
|
94
|
+
fn text;
|
|
95
|
+
r real[];
|
|
96
|
+
begin
|
|
97
|
+
if v is null then
|
|
98
|
+
raise exception 'meta.embed: there is no vector provider %', upper(p_provider);
|
|
99
|
+
end if;
|
|
100
|
+
if p_text is null then
|
|
101
|
+
return null;
|
|
102
|
+
end if;
|
|
103
|
+
if v->>'type' is distinct from 'function' then
|
|
104
|
+
raise exception 'meta.embed: vector provider % uses AI service %, which the pgkiln server calls: use it in a vector search configuration, a Retrieve tool or a vector column (Table Properties › Add vector column); in SQL only function providers embed',
|
|
105
|
+
v->>'name', v->>'service';
|
|
106
|
+
end if;
|
|
107
|
+
fn := v->>'function';
|
|
108
|
+
if fn !~ '^[A-Za-z_][A-Za-z0-9_]{0,62}\.[A-Za-z_][A-Za-z0-9_]{0,62}$' then
|
|
109
|
+
raise exception 'meta.embed: the function of vector provider % is schema.name', v->>'name';
|
|
110
|
+
end if;
|
|
111
|
+
execute format('select (%I.%I($1))::real[]', split_part(fn, '.', 1), split_part(fn, '.', 2)) into r using p_text;
|
|
112
|
+
if r is not null and cardinality(r) is distinct from (v->>'dimensions')::int then
|
|
113
|
+
raise exception 'meta.embed: % returned % dimensions, vector provider % has %', fn, cardinality(r), v->>'name', v->>'dimensions';
|
|
114
|
+
end if;
|
|
115
|
+
return r;
|
|
116
|
+
end
|
|
117
|
+
$$;
|
|
118
|
+
|
|
119
|
+
-- ---------------------------------------------------------------- keeping vector columns current
|
|
120
|
+
|
|
121
|
+
create table meta.embedding_queue (
|
|
122
|
+
id bigserial primary key,
|
|
123
|
+
app_id int not null references meta.app on delete cascade,
|
|
124
|
+
provider text not null,
|
|
125
|
+
-- the table whose trigger queued the row (pg_class oid)
|
|
126
|
+
tbl oid not null,
|
|
127
|
+
vector_column text not null,
|
|
128
|
+
key_column text not null,
|
|
129
|
+
source_columns text[] not null,
|
|
130
|
+
key_value text not null,
|
|
131
|
+
queued_at timestamptz not null default now(),
|
|
132
|
+
next_at timestamptz not null default now(),
|
|
133
|
+
attempts int not null default 0,
|
|
134
|
+
last_error text,
|
|
135
|
+
unique (tbl, vector_column, key_value)
|
|
136
|
+
);
|
|
137
|
+
create index on meta.embedding_queue (next_at);
|
|
138
|
+
create index on meta.embedding_queue (app_id, provider);
|
|
139
|
+
revoke all on meta.embedding_queue from public;
|
|
140
|
+
comment on table meta.embedding_queue is 'Rows whose vector column the pgkiln server re-embeds (136): queued by meta.embedding_enqueue() triggers and Backfill';
|
|
141
|
+
|
|
142
|
+
-- the trigger arguments: app id, provider, vector column, key column, source columns
|
|
143
|
+
create function meta.embedding_args_ok(p_args text[]) returns boolean
|
|
144
|
+
language sql immutable set search_path = pg_catalog as $$
|
|
145
|
+
select cardinality(p_args) between 5 and 24
|
|
146
|
+
and p_args[1] ~ '^[0-9]{1,10}$'
|
|
147
|
+
and p_args[2] ~ '^[A-Za-z][A-Za-z0-9_]{0,59}$'
|
|
148
|
+
and not exists (select from unnest(p_args[3:]) c where c !~ '^[a-z_][a-z0-9_$]{0,62}$')
|
|
149
|
+
$$;
|
|
150
|
+
|
|
151
|
+
-- the text a row is embedded from: its source columns, one per line
|
|
152
|
+
create function meta.embedding_text(p_row jsonb, p_columns text[]) returns text
|
|
153
|
+
language sql immutable set search_path = pg_catalog as $$
|
|
154
|
+
select nullif(concat_ws(E'\n', variadic array(select p_row->>c from unnest(p_columns) with ordinality u(c, n) order by n)), '')
|
|
155
|
+
$$;
|
|
156
|
+
|
|
157
|
+
-- BEFORE INSERT OR UPDATE OF …: a function provider's embedding, set at once (as the role that writes the row)
|
|
158
|
+
create function meta.embedding_set() returns trigger
|
|
159
|
+
language plpgsql volatile set search_path = meta, pg_catalog as $$
|
|
160
|
+
declare
|
|
161
|
+
v_args text[] := array(select unnest(tg_argv));
|
|
162
|
+
v_cols text[];
|
|
163
|
+
v_vec real[];
|
|
164
|
+
begin
|
|
165
|
+
if not meta.embedding_args_ok(v_args) then
|
|
166
|
+
raise exception 'meta.embedding_set: the arguments are app id, provider, vector column, key column, source columns';
|
|
167
|
+
end if;
|
|
168
|
+
v_cols := v_args[5:];
|
|
169
|
+
if tg_op = 'UPDATE' and meta.embedding_text(to_jsonb(old), v_cols) is not distinct from meta.embedding_text(to_jsonb(new), v_cols)
|
|
170
|
+
and to_jsonb(new)->>v_args[3] is not null then
|
|
171
|
+
return new;
|
|
172
|
+
end if;
|
|
173
|
+
v_vec := meta.embed(meta.embedding_text(to_jsonb(new), v_cols), v_args[2], v_args[1]::int);
|
|
174
|
+
new := jsonb_populate_record(new, jsonb_build_object(v_args[3], translate(v_vec::text, '{}', '[]')));
|
|
175
|
+
return new;
|
|
176
|
+
end
|
|
177
|
+
$$;
|
|
178
|
+
|
|
179
|
+
-- AFTER INSERT OR UPDATE OF …: the row's key into the queue (the server embeds it)
|
|
180
|
+
create function meta.embedding_enqueue() returns trigger
|
|
181
|
+
language plpgsql volatile security definer set search_path = meta, pg_catalog as $$
|
|
182
|
+
declare
|
|
183
|
+
v_args text[] := array(select unnest(tg_argv));
|
|
184
|
+
v_app int;
|
|
185
|
+
v_key text;
|
|
186
|
+
begin
|
|
187
|
+
if not meta.embedding_args_ok(v_args) then
|
|
188
|
+
raise exception 'meta.embedding_enqueue: the arguments are app id, provider, vector column, key column, source columns';
|
|
189
|
+
end if;
|
|
190
|
+
v_app := v_args[1]::int;
|
|
191
|
+
if not meta.app_visible(v_app) then
|
|
192
|
+
raise exception 'Application % is not in this workspace.', v_app using errcode = '42501';
|
|
193
|
+
end if;
|
|
194
|
+
if tg_op = 'UPDATE' and meta.embedding_text(to_jsonb(old), v_args[5:]) is not distinct from meta.embedding_text(to_jsonb(new), v_args[5:]) then
|
|
195
|
+
return null;
|
|
196
|
+
end if;
|
|
197
|
+
v_key := to_jsonb(new)->>v_args[4];
|
|
198
|
+
if v_key is null then
|
|
199
|
+
return null;
|
|
200
|
+
end if;
|
|
201
|
+
insert into meta.embedding_queue (app_id, provider, tbl, vector_column, key_column, source_columns, key_value)
|
|
202
|
+
values (v_app, upper(v_args[2]), tg_relid, v_args[3], v_args[4], v_args[5:], v_key)
|
|
203
|
+
on conflict (tbl, vector_column, key_value) do update
|
|
204
|
+
set app_id = excluded.app_id, provider = excluded.provider, key_column = excluded.key_column,
|
|
205
|
+
source_columns = excluded.source_columns, queued_at = now(), next_at = now(), attempts = 0, last_error = null;
|
|
206
|
+
return null;
|
|
207
|
+
end
|
|
208
|
+
$$;
|
|
209
|
+
|
|
210
|
+
grant execute on function meta.vector_provider(text, int), meta.embed(text, text, int), meta.embedding_args_ok(text[]),
|
|
211
|
+
meta.embedding_text(jsonb, text[]), meta.embedding_set(), meta.embedding_enqueue() to public;
|
|
212
|
+
|
|
213
|
+
-- ---------------------------------------------------------------- vector and hybrid search configurations
|
|
214
|
+
|
|
215
|
+
alter table meta.search_config
|
|
216
|
+
add column vector_provider text,
|
|
217
|
+
add column vector_column text,
|
|
218
|
+
add column key_column text,
|
|
219
|
+
add column distance text not null default 'cosine',
|
|
220
|
+
add column min_similarity real;
|
|
221
|
+
|
|
222
|
+
alter table meta.search_config drop constraint search_config_search_type_check;
|
|
223
|
+
alter table meta.search_config
|
|
224
|
+
add constraint search_config_search_type_check check (search_type in ('standard', 'fulltext', 'vector', 'hybrid')),
|
|
225
|
+
add constraint search_config_vector_provider_check check (vector_provider is null or vector_provider ~ '^[A-Z][A-Z0-9_]{0,59}$'),
|
|
226
|
+
-- column names only (quoted as identifiers by the server)
|
|
227
|
+
add constraint search_config_vector_column_check check (vector_column is null or vector_column ~ '^[A-Za-z_][A-Za-z0-9_$]{0,62}$'),
|
|
228
|
+
add constraint search_config_key_column_check check (key_column is null or key_column ~ '^[A-Za-z_][A-Za-z0-9_$]{0,62}$'),
|
|
229
|
+
add constraint search_config_distance_check check (distance in ('cosine', 'l2', 'inner_product')),
|
|
230
|
+
add constraint search_config_min_similarity_check check (min_similarity is null or min_similarity between -1 and 1),
|
|
231
|
+
add constraint search_config_vector_check check (search_type not in ('vector', 'hybrid') or (vector_provider is not null and vector_column is not null)),
|
|
232
|
+
add constraint search_config_hybrid_key_check check (search_type <> 'hybrid' or key_column is not null);
|
|
233
|
+
|
|
234
|
+
comment on column meta.search_config.search_type is 'standard (ILIKE per word), fulltext (websearch_to_tsquery, ts_rank_cd, ts_headline), vector (nearest by an embedding, 136) or hybrid (fulltext and vector by reciprocal rank fusion, 136)';
|
|
235
|
+
comment on column meta.search_config.vector_provider is 'vector, hybrid: the vector provider (static ID) that embeds the search text';
|
|
236
|
+
comment on column meta.search_config.vector_column is 'vector, hybrid: the source''s vector column';
|
|
237
|
+
comment on column meta.search_config.key_column is 'hybrid: the key that matches full-text and vector results';
|
|
238
|
+
comment on column meta.search_config.distance is 'vector, hybrid: cosine (<=>), l2 (<->) or inner_product (<#>)';
|
|
239
|
+
comment on column meta.search_config.min_similarity is 'vector, hybrid: at least this similarity (cosine: 1 - distance; inner product: the product; l2: 1 / (1 + distance))';
|
|
240
|
+
|
|
241
|
+
create or replace function meta.search_config_defaults() returns trigger
|
|
242
|
+
language plpgsql set search_path = pg_catalog as $$
|
|
243
|
+
begin
|
|
244
|
+
new.seq := coalesce(new.seq, 10);
|
|
245
|
+
new.source_type := coalesce(new.source_type, 'table');
|
|
246
|
+
new.searchable_columns := coalesce(new.searchable_columns, '{}');
|
|
247
|
+
new.max_results := coalesce(new.max_results, 10);
|
|
248
|
+
new.search_type := coalesce(new.search_type, 'standard');
|
|
249
|
+
new.did_you_mean := coalesce(new.did_you_mean, false);
|
|
250
|
+
new.distance := coalesce(new.distance, 'cosine');
|
|
251
|
+
return new;
|
|
252
|
+
end $$;
|