@kinetica/admin-agent 0.2.2 → 0.2.4
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/README.md
CHANGED
|
@@ -332,14 +332,14 @@ The `--bundle` flag points the agent at an **extracted** support-bundle director
|
|
|
332
332
|
|
|
333
333
|
Available against an extracted `gpudb_sysinfo` support bundle (see [Offline Bundle Mode](#offline-bundle-mode)). All read-only; the search/timeline tools stream and bound their output so a large rank log (tens of MB, hundreds of thousands of lines) never blows up the context.
|
|
334
334
|
|
|
335
|
-
| Tool | Description
|
|
336
|
-
| ------------------------------ |
|
|
337
|
-
| `kinetica_load_bundle` | Attach an extracted bundle directory; without a path it opens a directory picker (a model-supplied path needs operator confirmation)
|
|
338
|
-
| `kinetica_bundle_list_files` | Inventory: detected version, ranks + services present, file counts/sizes by kind, plus a layout-match verdict + per-file confidence for off-shape bundles — call this first
|
|
339
|
-
| `kinetica_bundle_log_timeline` | Per-time-bucket severity counts across ranks (the incident shape) — call before searching
|
|
340
|
-
| `kinetica_bundle_search_logs` | Bounded log search by regex, min-severity, time window, and rank / host-manager / component (reads both rolling and Loki-export logs)
|
|
341
|
-
| `kinetica_bundle_read_config` | Read the bundle's real on-disk `gpudb.conf`, with optional section/key filter
|
|
342
|
-
| `kinetica_bundle_read_sysinfo` | OS/process/version diagnostic files (memory, CPU, disk, GPU, network, process args)
|
|
335
|
+
| Tool | Description |
|
|
336
|
+
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
337
|
+
| `kinetica_load_bundle` | Attach an extracted bundle directory; without a path it opens a directory picker (a model-supplied path needs operator confirmation) |
|
|
338
|
+
| `kinetica_bundle_list_files` | Inventory: detected version, ranks + services present, file counts/sizes by kind, plus a layout-match verdict + per-file confidence for off-shape bundles — call this first |
|
|
339
|
+
| `kinetica_bundle_log_timeline` | Per-time-bucket severity counts across ranks (the incident shape) — call before searching |
|
|
340
|
+
| `kinetica_bundle_search_logs` | Bounded log search by regex, min-severity, time window, and rank / host-manager / component (reads both rolling and Loki-export logs); `include_multiline` stitches a multi-line record — e.g. a full `Executing SQL:` query whose embedded newlines span many lines — back onto each match |
|
|
341
|
+
| `kinetica_bundle_read_config` | Read the bundle's real on-disk `gpudb.conf`, with optional section/key filter |
|
|
342
|
+
| `kinetica_bundle_read_sysinfo` | OS/process/version diagnostic files (memory, CPU, disk, GPU, network, process args) |
|
|
343
343
|
|
|
344
344
|
### Reporting
|
|
345
345
|
|
|
@@ -417,7 +417,7 @@ References provide domain knowledge (not diagnostic runbooks). Create a `.md` fi
|
|
|
417
417
|
|
|
418
418
|
**Playbooks** (6): memory-pressure, gpu-out-of-memory, query-contention, resource-group-exhaustion, stale-rank, config-drift
|
|
419
419
|
|
|
420
|
-
**References** (
|
|
420
|
+
**References** (10):
|
|
421
421
|
|
|
422
422
|
- `gpudb-conf` — master config file structure, section index, tiered storage semantics
|
|
423
423
|
- `tiered-objects` — `ki_tiered_objects` schema, ID format, diagnostic queries
|
|
@@ -427,11 +427,12 @@ References provide domain knowledge (not diagnostic runbooks). Create a `.md` fi
|
|
|
427
427
|
- `mutation-safety` — pre-execution checklist for rebalance, alter-config, and DDL paths
|
|
428
428
|
- `sql-alter-table` — Kinetica 7.2 ALTER TABLE grammar, column property flags, shard-key immutability
|
|
429
429
|
- `sql-create-index` — column index syntax, chunk skip index, when to use which
|
|
430
|
+
- `sql-dialect` — PostgreSQL-baseline mental model + a "false friends" table of cross-dialect SQL that looks valid but fails in Kinetica (e.g. `TRY_CAST`/`SAFE_CAST`, backtick quoting, `NUMERIC` vs `DECIMAL`); steers remediation SQL away from SQL Server/Snowflake/Oracle idioms
|
|
430
431
|
- `version-quirks-7.2` — endpoint/property differences between 7.2.x and earlier releases
|
|
431
432
|
|
|
432
433
|
Plus a **bundle-scoped reference** (`support-bundle` — bundle layout, the two per-rank log families, raw + Loki-JSONL log-line formats, severity ordering, file parsing, crash-SQL forensics, and how to work an off-shape bundle via the `layout_match`/confidence signals) that lives in `knowledge/references/bundle/`. It loads in **every** session — even a pure live one — so that a bundle attached mid-session via `kinetica_load_bundle` has its parsing knowledge ready in the (build-once) prompt; the corpus is cached, so the cost to a session that never attaches a bundle is negligible.
|
|
433
434
|
|
|
434
|
-
> **Heads up — prompt budget:** all playbooks and references are front-loaded into a single system prompt at startup, so its token cost grows with the knowledge corpus. A startup tripwire (`agent/prompt-budget.ts`) prints the assembled prompt size under `DEBUG` and warns on stderr once it exceeds ~20,000 estimated tokens. Current baseline is ~
|
|
435
|
+
> **Heads up — prompt budget:** all playbooks and references are front-loaded into a single system prompt at startup, so its token cost grows with the knowledge corpus. A startup tripwire (`agent/prompt-budget.ts`) prints the assembled prompt size under `DEBUG` and warns on stderr once it exceeds ~20,000 estimated tokens. Current baseline is ~15.5k tokens (6 playbooks + 10 references). If you add substantial knowledge and trip that warning, treat it as the cue to switch from "load everything" to keyword-based playbook selection.
|
|
435
436
|
|
|
436
437
|
## Development
|
|
437
438
|
|
package/dist/admin-agent.js
CHANGED
|
@@ -4034,6 +4034,8 @@ function parseLogLine(line) {
|
|
|
4034
4034
|
|
|
4035
4035
|
// src/bundle/log-search.ts
|
|
4036
4036
|
var DEFAULT_MAX_MATCHES = 200;
|
|
4037
|
+
var MULTILINE_MAX_LINES = 300;
|
|
4038
|
+
var MULTILINE_MAX_CHARS = 2e4;
|
|
4037
4039
|
var REGEX_SCAN_MAX = 8192;
|
|
4038
4040
|
var GRANULARITY_LEN = {
|
|
4039
4041
|
day: 10,
|
|
@@ -4075,6 +4077,23 @@ function matchesFilters(parsed, query3, regex, minRank) {
|
|
|
4075
4077
|
return false;
|
|
4076
4078
|
return true;
|
|
4077
4079
|
}
|
|
4080
|
+
function buildMatch(lineNumber, parsed) {
|
|
4081
|
+
return {
|
|
4082
|
+
lineNumber,
|
|
4083
|
+
...parsed.timestamp !== void 0 ? { timestamp: parsed.timestamp } : {},
|
|
4084
|
+
...parsed.severity !== void 0 ? { severity: parsed.severity } : {},
|
|
4085
|
+
...parsed.rank !== void 0 ? { rank: parsed.rank } : {},
|
|
4086
|
+
message: parsed.message,
|
|
4087
|
+
raw: parsed.raw
|
|
4088
|
+
};
|
|
4089
|
+
}
|
|
4090
|
+
function finalizeMultiline(pending) {
|
|
4091
|
+
if (pending.extra.length === 0) return pending.base;
|
|
4092
|
+
const joined = pending.extra.join("\n");
|
|
4093
|
+
const suffix = pending.truncated ? "\n\u2026 [continuation truncated]" : "";
|
|
4094
|
+
return { ...pending.base, message: `${pending.base.message}
|
|
4095
|
+
${joined}${suffix}` };
|
|
4096
|
+
}
|
|
4078
4097
|
async function searchLogFile(filePath, query3) {
|
|
4079
4098
|
const maxMatches = query3.maxMatches ?? DEFAULT_MAX_MATCHES;
|
|
4080
4099
|
const minRank = query3.minSeverity !== void 0 ? severityRank(query3.minSeverity) : -Infinity;
|
|
@@ -4096,9 +4115,17 @@ async function searchLogFile(filePath, query3) {
|
|
|
4096
4115
|
...query3.fromTs !== void 0 ? { fromTs: floorTimestamp(query3.fromTs) } : {},
|
|
4097
4116
|
...query3.toTs !== void 0 ? { toTs: ceilTimestamp(query3.toTs) } : {}
|
|
4098
4117
|
};
|
|
4118
|
+
const coalesce = query3.coalesceMultiline === true;
|
|
4099
4119
|
const matches = [];
|
|
4100
4120
|
let totalMatched = 0;
|
|
4101
4121
|
let linesScanned = 0;
|
|
4122
|
+
let pending;
|
|
4123
|
+
const flushPending = () => {
|
|
4124
|
+
if (pending) {
|
|
4125
|
+
matches.push(finalizeMultiline(pending));
|
|
4126
|
+
pending = void 0;
|
|
4127
|
+
}
|
|
4128
|
+
};
|
|
4102
4129
|
try {
|
|
4103
4130
|
const rl = (0, import_node_readline.createInterface)({
|
|
4104
4131
|
input: (0, import_node_fs4.createReadStream)(filePath, { encoding: "utf-8" }),
|
|
@@ -4107,20 +4134,29 @@ async function searchLogFile(filePath, query3) {
|
|
|
4107
4134
|
for await (const line of rl) {
|
|
4108
4135
|
linesScanned++;
|
|
4109
4136
|
const parsed = parseLogLine(line);
|
|
4137
|
+
if (pending) {
|
|
4138
|
+
if (parsed.timestamp === void 0) {
|
|
4139
|
+
if (!pending.truncated && pending.extra.length < MULTILINE_MAX_LINES && pending.chars + line.length + 1 <= MULTILINE_MAX_CHARS) {
|
|
4140
|
+
pending.extra.push(line);
|
|
4141
|
+
pending.chars += line.length + 1;
|
|
4142
|
+
} else {
|
|
4143
|
+
pending.truncated = true;
|
|
4144
|
+
}
|
|
4145
|
+
continue;
|
|
4146
|
+
}
|
|
4147
|
+
flushPending();
|
|
4148
|
+
}
|
|
4110
4149
|
if (!matchesFilters(parsed, boundedQuery, regex, minRank)) continue;
|
|
4111
4150
|
totalMatched++;
|
|
4112
4151
|
if (matches.length < maxMatches) {
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
...parsed.severity !== void 0 ? { severity: parsed.severity } : {},
|
|
4117
|
-
...parsed.rank !== void 0 ? { rank: parsed.rank } : {},
|
|
4118
|
-
message: parsed.message,
|
|
4119
|
-
raw: parsed.raw
|
|
4120
|
-
});
|
|
4152
|
+
const base = buildMatch(linesScanned, parsed);
|
|
4153
|
+
if (coalesce) pending = { base, extra: [], chars: 0, truncated: false };
|
|
4154
|
+
else matches.push(base);
|
|
4121
4155
|
}
|
|
4122
4156
|
}
|
|
4157
|
+
flushPending();
|
|
4123
4158
|
} catch (err) {
|
|
4159
|
+
flushPending();
|
|
4124
4160
|
const message = err instanceof Error ? err.message : String(err);
|
|
4125
4161
|
return {
|
|
4126
4162
|
matches,
|
|
@@ -4508,7 +4544,8 @@ function toLineQuery(q) {
|
|
|
4508
4544
|
...q.minSeverity !== void 0 ? { minSeverity: q.minSeverity } : {},
|
|
4509
4545
|
...q.fromTs !== void 0 ? { fromTs: q.fromTs } : {},
|
|
4510
4546
|
...q.toTs !== void 0 ? { toTs: q.toTs } : {},
|
|
4511
|
-
...q.maxMatches !== void 0 ? { maxMatches: q.maxMatches } : {}
|
|
4547
|
+
...q.maxMatches !== void 0 ? { maxMatches: q.maxMatches } : {},
|
|
4548
|
+
...q.coalesceMultiline !== void 0 ? { coalesceMultiline: q.coalesceMultiline } : {}
|
|
4512
4549
|
};
|
|
4513
4550
|
}
|
|
4514
4551
|
function toTimelineLineQuery(q) {
|
|
@@ -4847,6 +4884,9 @@ var BundleSearchLogsSchema = import_zod20.z.object({
|
|
|
4847
4884
|
host_manager: import_zod20.z.boolean().describe("Search the host-manager (hm) log \u2014 a singleton service, not a rank.").optional(),
|
|
4848
4885
|
component: import_zod20.z.string().optional(),
|
|
4849
4886
|
include_components: import_zod20.z.boolean().optional(),
|
|
4887
|
+
include_multiline: import_zod20.z.boolean().describe(
|
|
4888
|
+
"Reconstruct multi-line log records: append continuation lines (those with no timestamp) to each match. Use this to capture a full SQL statement on an 'Executing SQL:' line \u2014 the query often spans many lines because the SQL has embedded newlines, and a plain match shows only its first line. Works on the rolling core logs (logs-local/); Loki per-rank tails (logs/rankN.log) keep only the statement's first line, so there are no continuation lines to stitch there."
|
|
4889
|
+
).optional(),
|
|
4850
4890
|
max_matches: import_zod20.z.number().int().min(1).max(1e3).optional()
|
|
4851
4891
|
});
|
|
4852
4892
|
async function bundleSearchLogs(source, args = {}) {
|
|
@@ -4859,6 +4899,7 @@ async function bundleSearchLogs(source, args = {}) {
|
|
|
4859
4899
|
...args.host_manager !== void 0 ? { hostManager: args.host_manager } : {},
|
|
4860
4900
|
...args.component !== void 0 ? { component: args.component } : {},
|
|
4861
4901
|
...args.include_components !== void 0 ? { includeComponents: args.include_components } : {},
|
|
4902
|
+
...args.include_multiline !== void 0 ? { coalesceMultiline: args.include_multiline } : {},
|
|
4862
4903
|
...args.max_matches !== void 0 ? { maxMatches: args.max_matches } : {}
|
|
4863
4904
|
};
|
|
4864
4905
|
const result = await source.searchLogs(query3);
|
|
@@ -20,6 +20,7 @@ Severity order for filtering is `WARN < UERR < ERROR < FATAL`, so `min_severity=
|
|
|
20
20
|
|
|
21
21
|
- The logs are large (a rank log can exceed 100k lines). NEVER ask for a whole file. Use `kinetica_bundle_log_timeline` to localize, then `kinetica_bundle_search_logs` with a tight time window + severity to extract only relevant lines. The match cap is shared across files — if you see "capped", narrow the query rather than asking for more.
|
|
22
22
|
- You can pass a timeline bucket label straight into `from_ts`/`to_ts` (e.g. `2026-06-11 15` searches that whole hour) — partial timestamps are widened to cover the full period.
|
|
23
|
+
- A single log record can span multiple physical lines when a logged value (notably a SQL statement) contains embedded newlines — the continuation lines have no timestamp. A plain search returns only the first line. Pass `include_multiline: true` to stitch the continuation lines back onto each match and recover the whole record. See "Finding a crash's triggering SQL".
|
|
23
24
|
- Timestamps are plain local strings without a timezone; compare them lexically and treat cross-rank timing cautiously.
|
|
24
25
|
- **Ranks vs. the host manager:** `rank` selects a numeric rank (`r0`, `r1`, …) only. The host manager (`core-gpudb-rolling-hm.log`) is a singleton service, NOT a rank — search or timeline it with `host_manager: true`, never `rank: "hm"`. By default both `log_timeline` and `search_logs` already cover the host manager along with the numeric ranks; `kinetica_bundle_list_files` lists it under `services_present`.
|
|
25
26
|
|
|
@@ -29,11 +30,13 @@ When a worker rank segfaults mid-query, that rank's log holds the **backtrace**
|
|
|
29
30
|
|
|
30
31
|
Workflow, given a `JobId` from a worker's crash stack:
|
|
31
32
|
|
|
32
|
-
1. `kinetica_bundle_search_logs` with `rank: "r0"
|
|
33
|
-
2.
|
|
34
|
-
3. **
|
|
33
|
+
1. `kinetica_bundle_search_logs` with `rank: "r0"`, `regex` = the JobId, **and `include_multiline: true`**. r0 logs the `/execute/sql` receipt (submitting user), the `Sql/SqlDriver.cpp … Executing SQL:` line, and per-operation endpoint lines.
|
|
34
|
+
2. **Read the full statement straight from the `Executing SQL:` line — do not reconstruct it.** Kinetica logs the SQL verbatim, so a real query spans MANY physical lines: `FROM …`, `JOIN …`, `WHERE …` each land on their own line with no timestamp prefix. Those continuation lines belong to the same log record; `include_multiline: true` stitches them back onto the match so you see the WHOLE query. WITHOUT it, a match is only the first physical line (e.g. `… Executing SQL: SELECT c."circuitId", c."circuitRef"`) and you would wrongly report the query as "truncated." Quote the statement verbatim from this single (now multi-line) match.
|
|
35
|
+
3. **Cache-hit fallback only:** if `Found plan for the SQL in cache` precedes the job, Kinetica logs `Executing SQL:` as just the statement keyword (e.g. a bare `SELECT` or `EXECUTE PROCEDURE …`) with no continuation lines to stitch. ONLY then fall back to the per-operation endpoint lines (`Endpoint_aggregate_group_by.cpp`, filter/join endpoints): they carry `table:`, `column_names:`/`aliases:` (the SELECT list), and `expr:` (the full WHERE predicate), whose values survive a cache hit. A `datetime()`/timestamp filter showing up here often _is_ the input that triggered a parser segfault.
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
**Where the full multi-line query actually lives:** `include_multiline` recovers the whole statement only from the **rolling core logs** (`logs-local/core-gpudb-rolling-r0.log`), where the SQL's embedded newlines are preserved as continuation lines. The **Loki per-rank tail** (`logs/rank0.log`) keeps only the statement's first physical line — promtail captures each line as its own record, so the `FROM`/`JOIN`/`WHERE` lines are simply not in that export, and nothing can stitch them. So this workflow depends on r0 being present under `logs-local/`. If r0 exists only as a Loki tail (rare for the coordinator, but possible for a Loki-only bundle), the complete query may not be in the bundle at all — say so rather than reporting the first line as the whole query, and fall back to step 3's endpoint lines.
|
|
38
|
+
|
|
39
|
+
See `rank-architecture.md` (Where queries are logged) for why this locality holds, and "Two log families" below for rolling-vs-Loki precedence.
|
|
37
40
|
|
|
38
41
|
### Files of interest
|
|
39
42
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Kinetica SQL Dialect — PostgreSQL Baseline & False Friends
|
|
3
|
+
category: sql-syntax
|
|
4
|
+
keywords:
|
|
5
|
+
[
|
|
6
|
+
sql-dialect,
|
|
7
|
+
postgresql,
|
|
8
|
+
false-friends,
|
|
9
|
+
try-cast,
|
|
10
|
+
safe-cast,
|
|
11
|
+
cast,
|
|
12
|
+
convert,
|
|
13
|
+
remediation-sql,
|
|
14
|
+
datediff,
|
|
15
|
+
timestamp,
|
|
16
|
+
nested-aggregate,
|
|
17
|
+
identifiers,
|
|
18
|
+
backticks,
|
|
19
|
+
decimal,
|
|
20
|
+
numeric,
|
|
21
|
+
]
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Mental Model — Start from PostgreSQL
|
|
25
|
+
|
|
26
|
+
Kinetica SQL is **PostgreSQL-compatible**: treat standard PostgreSQL syntax,
|
|
27
|
+
functions, and behavior as the baseline. The deviations documented here (and in
|
|
28
|
+
`version-quirks-7.2.md`) **override** that baseline. When no Kinetica-specific
|
|
29
|
+
rule applies, the PostgreSQL form is the safe default.
|
|
30
|
+
|
|
31
|
+
**The common failure when recommending remediation SQL is importing idioms from
|
|
32
|
+
OTHER dialects** — SQL Server, Snowflake, Oracle, MySQL. Those are not the
|
|
33
|
+
baseline; PostgreSQL is. The table below lists imports that look valid but fail
|
|
34
|
+
in Kinetica.
|
|
35
|
+
|
|
36
|
+
> Dialect facts adapted from the official `kineticadb/agent-skills` knowledge
|
|
37
|
+
> base (Apache-2.0).
|
|
38
|
+
|
|
39
|
+
## False Friends — Looks Valid, FAILS in Kinetica
|
|
40
|
+
|
|
41
|
+
Do NOT put any of these in a remediation suggestion. Use the Kinetica form.
|
|
42
|
+
|
|
43
|
+
| Looks valid (other dialect) | Why it fails | Use instead |
|
|
44
|
+
| -------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
45
|
+
| `TRY_CAST(x AS t)` / `SAFE_CAST(x, t)` | No error-tolerant cast exists in Kinetica | `CAST(x AS t)` or `CONVERT(x, t)`; shorthand `INT(x)`, `DOUBLE(x)`, `STRING(x)` |
|
|
46
|
+
| `` `ident` `` (backtick quoting) | Backticks are not a valid identifier quote | ANSI double quotes: `"ident"` |
|
|
47
|
+
| `ts1 - ts2` (timestamp subtraction) | Timestamp arithmetic with `-` is not supported | `DATEDIFF('unit', ts1, ts2)` |
|
|
48
|
+
| `NUMERIC(p, s)` | The type is named `DECIMAL`, not `NUMERIC` | `DECIMAL(p, s)` (max precision 27, max scale 18) |
|
|
49
|
+
| `SUM(COUNT(*))` (nested aggregates) | Fails with "Aggregate expressions cannot be nested" | Separate into CTEs — window/aggregate in different stages |
|
|
50
|
+
| `ANALYZE TABLE t` | No cost-based optimizer stats (see `version-quirks-7.2.md`) | No equivalent — do NOT suggest a "refresh table stats" step |
|
|
51
|
+
| `SELECT ... ;` (trailing semicolon) | A trailing `;` is rejected | Omit the trailing semicolon |
|
|
52
|
+
| `ORDER BY <array_col>` | Cannot sort by an `array<...>` column | Index an element (`ORDER BY "col"[1]`) or sort by a scalar column |
|
|
53
|
+
|
|
54
|
+
`TRY_CAST` / `SAFE_CAST` warrant special note: they come from SQL Server,
|
|
55
|
+
Snowflake, and BigQuery, and Kinetica has no cast variant that returns NULL on
|
|
56
|
+
conversion failure. If a value might not convert cleanly, filter the source rows
|
|
57
|
+
(`WHERE` / `CASE`) before casting rather than reaching for a non-existent
|
|
58
|
+
`TRY_*` function.
|
|
59
|
+
|
|
60
|
+
## Type Conversion — the Valid Forms
|
|
61
|
+
|
|
62
|
+
- Standard `CAST(expr AS type)` and `CONVERT(expr, type)` both work.
|
|
63
|
+
- Shorthand cast functions: `INT(expr)`, `LONG(expr)`, `DOUBLE(expr)`,
|
|
64
|
+
`FLOAT(expr)`, `DECIMAL(expr)`, `STRING(expr)`, `ULONG(expr)`.
|
|
65
|
+
- `JSON_EXTRACT_VALUE` always returns TEXT — you MUST cast for numeric use:
|
|
66
|
+
`CAST(JSON_EXTRACT_VALUE("payload", '$.count') AS INTEGER) > 100`.
|
|
67
|
+
|
|
68
|
+
## Date / Time — Use Functions, Not Arithmetic
|
|
69
|
+
|
|
70
|
+
| Kinetica form | Replaces (PostgreSQL / other) |
|
|
71
|
+
| ------------------------------------ | --------------------------------- |
|
|
72
|
+
| `DATEDIFF('unit', start, end)` | `EXTRACT(EPOCH FROM end - start)` |
|
|
73
|
+
| `DATEADD('unit', amount, ts)` | `ts + INTERVAL '...'` |
|
|
74
|
+
| `TIME_BUCKET(INTERVAL 'n' UNIT, ts)` | `date_bin()` |
|
|
75
|
+
|
|
76
|
+
Units: `SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR` (also
|
|
77
|
+
`MICROSECOND` / `MILLISECOND`). INTERVAL syntax: `INTERVAL '30' MINUTE`.
|
|
78
|
+
|
|
79
|
+
## Identifier & Statement Hygiene
|
|
80
|
+
|
|
81
|
+
- **Double quotes only** for identifiers (`"my_col"`) — never backticks.
|
|
82
|
+
- **Identifiers are case-sensitive** — `"UserID"` ≠ `"userid"`. Verify column
|
|
83
|
+
names against the discovered schema before recommending SQL.
|
|
84
|
+
- **Fully-qualify table names** — `"schema"."table"`.
|
|
85
|
+
- **No trailing semicolons.**
|
|
86
|
+
|
|
87
|
+
## Kinetica Conveniences (valid, non-obvious)
|
|
88
|
+
|
|
89
|
+
- `SELECT * EXCLUDE (col1, col2)` — wildcard minus specific columns.
|
|
90
|
+
- `IF(cond, a, b)` — ternary (PostgreSQL has only `CASE`).
|
|
91
|
+
- `NVL(x, default)` / `NVL2(x, not_null, null_val)` — null handling.
|
|
92
|
+
- `DECODE(expr, m1, v1, ..., default)` — pattern matching.
|
|
93
|
+
|
|
94
|
+
## When Unsure — Verify Empirically
|
|
95
|
+
|
|
96
|
+
The live database is the source of truth. Before recommending any remediation
|
|
97
|
+
SQL whose syntax you are not certain Kinetica supports, validate it with
|
|
98
|
+
`kinetica_explain_query` against the live instance. If it cannot be validated
|
|
99
|
+
(or there is no live connection), label the suggestion as unverified rather than
|
|
100
|
+
asserting it is correct.
|