Demo, show me the demo! Make your power points and blog posts come to life! Demonstrating a REST API via cURL or Postman can be tedious, or even boring.
If I had time, I’d make a local web app to show it off. But since I don’t have much time, I’ll do the next best thing, have my AI Agent build an app for me!
I have Oracle AI Database (26ai, but 19c, 21c, or 23ai are fine too.) I have Oracle REST Data Services (ORDS) 25.4. I have a table, EMPLOYEE. It is registered for ORDS, to enable the AutoREST feature.
Our Oracle AI Database REST API tables make working with your data easy. I can EARN
{
"openapi": "3.0.0",
"info":
"title": "ORDS generated API for EMPLOYEES",
"version": "1.0.0"
,
"servers": [
"url": "http://localhost:8080/ords/hr/employees"
],
"paths": {
"/": {
"delete":
"description": "Remove a record from EMPLOYEES",
"responses":
"204":
"description": "Deleted result."
,
"parameters": [
"name": "q",
"in": "query",
"schema":
"type": "string"
,
"description": "JSON QBE parameter."
]
,
"get": {
"description": "Retrieve records from EMPLOYEES",
"responses": {
"200": {
"description": "The queried record.",
"content": {
"application/json": {
"schema":
"type": "object",
"properties":
"items":
"type": "array",
"items":
"$ref": "#/components/schemas/employees_ITEM"
}
}
}
},
"parameters": [
"name": "q",
"in": "query",
"schema":
"type": "string"
,
"description": "JSON QBE parameter."
]
},
"post": {
"description": "Create a new record on EMPLOYEES",
"responses": {
"201":
"description": "The successfully created record.",
"content":
"application/json":
"schema":
"$ref": "#/components/schemas/employees_ITEM"
},
"parameters": [],
"requestBody":
"content":
"application/json":
"schema":
"$ref": "#/components/schemas/employees_ITEM"
}
},
"/batchload": {
"post": {
"description": "Create new records on EMPLOYEES",
"responses": {
"200": {
"description": "The status of the processed records.",
"content":
"application/json":
"schema":
"type": "object",
"properties":
}
},
"parameters": [],
"requestBody":
"content":
"text/csv":
"schema":
}
},
"/id": {
"delete":
"description": "Remove a record from EMPLOYEES",
"responses":
"204":
"description": "Deleted result."
,
"parameters": [
"name": "id",
"in": "path",
"required": true,
"schema":
"type": "string",
"pattern": "^[^/]+$"
,
"description": "Primary key(s): employee_id"
]
,
"get": {
"description": "Retrieve a record from EMPLOYEES",
"responses": {
"200":
"description": "The queried record.",
"content":
"application/json":
"schema":
"$ref": "#/components/schemas/employees_ITEM"
},
"parameters": [
"name": "id",
"in": "path",
"required": true,
"schema":
"type": "string",
"pattern": "^[^/]+$"
,
"description": "Primary key(s): employee_id"
]
},
"put": {
"description": "Create or update a record on EMPLOYEES",
"responses": {
"200":
"description": "The successfully updated record.",
"content":
"application/json":
"schema":
"$ref": "#/components/schemas/employees_ITEM"
,
"201":
"description": "The successfully created record.",
"content":
"application/json":
"schema":
"$ref": "#/components/schemas/employees_ITEM"
},
"parameters": [
"name": "id",
"in": "path",
"required": true,
"schema":
"type": "string",
"pattern": "^[^/]+$"
,
"description": "Primary key(s): employee_id"
],
"requestBody":
"content":
"application/json":
"schema":
"$ref": "#/components/schemas/employees_ITEM"
}
}
},
"components": {
"schemas":
"DATE": ([-+][012]\\d:[0-5]\\d))$"
,
"NUMBER":
"type": "number"
,
"VARCHAR2":
"type": "string"
,
"employees_ITEM":
"properties":
"commission_pct":
"$ref": "#/components/schemas/NUMBER"
,
"department_id":
"$ref": "#/components/schemas/NUMBER"
,
"email":
"$ref": "#/components/schemas/VARCHAR2"
,
"employee_id":
"x-ords-primary-key": "true",
"$ref": "#/components/schemas/NUMBER"
,
"first_name":
"$ref": "#/components/schemas/VARCHAR2"
,
"hire_date":
"$ref": "#/components/schemas/DATE"
,
"job_id":
"$ref": "#/components/schemas/VARCHAR2"
,
"last_name":
"$ref": "#/components/schemas/VARCHAR2"
,
"manager_id":
"$ref": "#/components/schemas/NUMBER"
,
"phone_number":
"$ref": "#/components/schemas/VARCHAR2"
,
"salary":
"$ref": "#/components/schemas/NUMBER"
}
}
About 5 minutes later, I had a working app.
Quite a lot, meaning I needed to make the following corrections in the follow-up instructions:
My app lets me toggle/enter filters and sorting criteria, and it simply prints the QBE JSON, and previews the full ORDS URL for the GET request.
And our application takes that input and decides that it can be obtained via this QBE JSON payload in the query parameters:
"$and": [
"last_name":
"$like": "S%"
,
"job_id":
"$eq": "SA_REP"
,
"salary":
"$gte": 7000
,
"hire_date":
"$gte":
"$date": "2014-01-01T00:00:00Z"
,
"hire_date":
"$lte":
"$date": "2015-06-01T23:59:59Z"
,
"commission_pct":
"$notnull": null
],
"$orderby":
"LAST_NAME": "desc"
My web page source is below. I did QA for about 10 minutes, so I’m sure more cleanup is needed, but it’s a good start.
The interesting part of the code above is the FILTER BUILDER section which starts at line 700.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ORDS Query Filter Explorer — HR.EMPLOYEES</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;1,9..40,300&family=JetBrains+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root
--bg: #080a0d;
--bg1: #0e1218;
--bg2: #141b24;
--bg3: #1c2530;
--border: #232d3a;
--border2:#2e3d50;
--red: #c94236;
--red-hl: #e04535;
--cyan: #22d3ee;
--amber: #f59e0b;
--green: #22c55e;
--purple: #a78bfa;
--text: #dde5f0;
--text2: #8fa3b8;
--text3: #404f62;
--syne: 'Syne', sans-serif;
--body: 'DM Sans', sans-serif;
--mono: 'JetBrains Mono', monospace;
*,*::before,*::afterbox-sizing:border-box;margin:0;padding:0
body
font-family: var(--body);
background: var(--bg);
color: var(--text);
min-height: 100vh;
font-size: 14px;
line-height: 1.6;
/* ── SCROLLBAR ── */
::-webkit-scrollbarwidth:4px;height:4px
::-webkit-scrollbar-trackbackground:var(--bg1)
::-webkit-scrollbar-thumbbackground:var(--border2);border-radius:2px
/* ── HEADER ── */
.header
background: var(--bg1);
border-bottom: 1px solid var(--border);
padding: 0 28px;
height: 58px;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 100;
.hb display:flex; align-items:center; gap:14px
.oracle-badge
background: var(--red);
color: #fff;
font-family: var(--syne);
font-size: 10px;
font-weight: 800;
letter-spacing: .12em;
padding: 4px 9px;
text-transform: uppercase;
clip-path: polygon(0 0,calc(100% - 6px) 0,100% 100%,0 100%);
.header-title font-family:var(--syne); font-size:15px; font-weight:700; letter-spacing:.02em
.header-sub font-family:var(--mono); font-size:10px; color:var(--text3); letter-spacing:.05em
.endpoint-pill
display:flex; align-items:center; gap:8px;
background:var(--bg2); border:1px solid var(--border);
padding:5px 12px; border-radius:2px;
font-family:var(--mono); font-size:11px; color:var(--cyan);
.dot-live width:6px;height:6px;background:var(--green);border-radius:50%;animation:blink 2s ease-in-out infinite
@keyframes blink0%,100%opacity:150%opacity:.3
/* ── LAYOUT ── */
.app
display: grid;
grid-template-columns: var(--sidebar-w, 320px) 4px 1fr;
grid-template-rows: auto 1fr;
height: calc(100vh - 58px);
/* ── RESIZE HANDLE ── */
.resizer
grid-row: 1 / 3;
background: var(--border);
cursor: col-resize;
transition: background .15s;
position: relative;
z-index: 10;
.resizer:hover, .resizer.dragging background: var(--cyan);
.resizer::after
content: '';
position: absolute;
inset: 0 -4px; /* wider invisible hit target */
/* ── SIDEBAR ── */
.sidebar
grid-row: 1 / 3;
background: var(--bg1);
border-right: none;
overflow-y: auto;
padding: 20px 18px;
display: flex;
flex-direction: column;
gap: 18px;
min-width: 220px;
max-width: 600px;
.sec-label
font-family:var(--syne); font-size:9px; font-weight:800;
letter-spacing:.14em; text-transform:uppercase; color:var(--text3);
display:flex; align-items:center; gap:8px; margin-bottom:10px;
.sec-label::aftercontent:'';flex:1;height:1px;background:var(--border)
.fg display:flex; flex-direction:column; gap:10px
.fr display:grid; grid-template-columns:1fr 1fr; gap:8px
.field display:flex; flex-direction:column; gap:3px
.fl font-family:var(--mono); font-size:9px; color:var(--text2); letter-spacing:.06em; text-transform:uppercase
.field input, .field select
background:var(--bg2); border:1px solid var(--border);
color:var(--text); padding:6px 9px;
font-family:var(--mono); font-size:12px;
border-radius:2px; outline:none; width:100%;
transition:border-color .15s, box-shadow .15s;
.field input:focus,.field select:focusborder-color:var(--cyan);box-shadow:0 0 0 2px rgba(34,211,238,.08)
.field input::placeholdercolor:var(--text3)
.field select optionbackground:var(--bg2)
.helpfont-family:var(--mono);font-size:9px;color:var(--text3);line-height:1.5;margin-top:1px
/* operator inline select */
.op-sel
background:var(--bg2); border:1px solid var(--border);
color:var(--amber); padding:6px 5px;
font-family:var(--mono); font-size:10px;
border-radius:2px; outline:none; cursor:pointer; flex-shrink:0;
/* full-width stacked operator select */
.full-op
width: 100%;
padding: 6px 9px;
font-size: 11px;
color: var(--amber);
background: var(--bg2);
border: 1px solid var(--border);
border-radius: 2px;
outline: none;
cursor: pointer;
transition: border-color .15s;
.full-op:focus border-color: var(--cyan); box-shadow: 0 0 0 2px rgba(34,211,238,.08);
.full-op option background: var(--bg2);
.inline-pair display:flex; gap:4px; align-items:stretch
.inline-pair .op-sel min-width:82px
.inline-pair input flex:1; min-width:0
/* toggle pairs */
.toggle
display:flex; background:var(--bg2);
border:1px solid var(--border); border-radius:2px; overflow:hidden;
.tgl-btn
flex:1; padding:5px 8px;
background:transparent; border:none;
color:var(--text2); font-family:var(--syne); font-size:10px;
font-weight:700; letter-spacing:.08em; cursor:pointer; transition:all .15s;
text-align:center;
.tgl-btn.onbackground:var(--red);color:#fff
/* commission row */
.comm-row display:flex; gap:5px
.comm-btn
flex:1; padding:5px; background:var(--bg2);
border:1px solid var(--border); color:var(--text2);
font-family:var(--mono); font-size:9px; cursor:pointer;
transition:all .15s; text-align:center; border-radius:2px;
.comm-btn.onborder-color:var(--cyan);color:var(--cyan);background:rgba(34,211,238,.06)
/* action buttons */
.btn-exec
width:100%; padding:10px; background:var(--red); color:#fff;
border:none; font-family:var(--syne); font-size:12px; font-weight:800;
letter-spacing:.07em; text-transform:uppercase; cursor:pointer;
transition:all .15s; border-radius:2px;
.btn-exec:hoverbackground:var(--red-hl);box-shadow:0 4px 22px rgba(201,66,54,.4)
.btn-reset
width:100%; padding:7px; background:transparent; color:var(--text2);
border:1px solid var(--border); font-family:var(--syne); font-size:10px;
font-weight:700; letter-spacing:.07em; text-transform:uppercase;
cursor:pointer; transition:all .15s; border-radius:2px;
.btn-reset:hoverborder-color:var(--border2);color:var(--text)
/* ── QUERY PANEL ── */
.qpanel
background:var(--bg1); border-bottom:1px solid var(--border);
padding:14px 22px; grid-column: 3;
.qpanel-inner display:flex; gap:14px; align-items:flex-start
.qblocks flex:1; display:flex; flex-direction:column; gap:8px
.qblock
background:var(--bg); border:1px solid var(--border);
border-radius:2px; padding:10px 14px; position:relative;
.qblock.cyan border-left:3px solid var(--cyan)
.qblock.amber border-left:3px solid var(--amber)
.qbl font-family:var(--mono); font-size:8px; letter-spacing:.12em; color:var(--text3); text-transform:uppercase; margin-bottom:5px
.qtext
font-family:var(--mono); font-size:11px; color:var(--cyan);
word-break:break-all; max-height:56px; overflow-y:auto;
white-space:pre-wrap; line-height:1.55;
.utext font-family:var(--mono); font-size:10px; color:var(--text2); word-break:break-all; line-height:1.5
.utext .ubcolor:var(--text3) .utext .upcolor:var(--amber) .utext .uvcolor:var(--cyan)
.badges display:flex; flex-wrap:wrap; gap:5px; margin-top:2px
.badge
display:inline-flex; align-items:center; gap:4px;
background:var(--bg3); border:1px solid var(--border2);
padding:2px 7px; border-radius:2px;
font-family:var(--mono); font-size:9px;
.badge .bccolor:var(--amber) .badge .bocolor:var(--text3) .badge .bvcolor:var(--cyan)
.qactions display:flex; flex-direction:column; gap:5px
.cbtn
padding:6px 12px; background:var(--bg2); border:1px solid var(--border);
color:var(--text2); font-family:var(--mono); font-size:10px;
cursor:pointer; transition:all .15s; border-radius:2px; white-space:nowrap;
.cbtn:hoverborder-color:var(--cyan);color:var(--cyan)
.cbtn.open:hoverborder-color:var(--amber);color:var(--amber)
/* ── MAIN ── */
.main
padding: 14px 22px 0 22px;
overflow: hidden; /* main itself does NOT scroll */
display: flex;
flex-direction: column;
gap: 10px;
grid-column: 3;
min-height: 0; /* critical: lets flex children shrink below content size */
.results-bar display:flex; align-items:center; justify-content:space-between; flex-shrink:0
.count-pill
display:flex; align-items:center; gap:6px;
background:var(--bg2); border:1px solid var(--border);
padding:4px 12px; border-radius:2px;
font-family:var(--mono); font-size:11px;
.count-numcolor:var(--amber);font-weight:500
.page-locfont-family:var(--mono);font-size:10px;color:var(--text2)
/* table wrapper — fills remaining height, clips overflow */
.tbl-wrap
background:var(--bg1); border:1px solid var(--border);
border-radius:2px;
display: flex;
flex-direction: column;
flex: 1 1 0; /* grow to fill, but never push beyond parent */
min-height: 0;
overflow: hidden;
margin-bottom: 14px;
/* scrollable zone — only the rows scroll, thead stays put */
.tbl-scroll
overflow-x: auto;
overflow-y: auto;
flex: 1 1 0;
min-height: 0;
table width:100%; border-collapse:collapse
thead th
background:var(--bg2); padding:9px 13px;
font-family:var(--syne); font-size:9px; font-weight:800;
letter-spacing:.11em; text-transform:uppercase;
color:var(--text3); border-bottom:1px solid var(--border);
white-space:nowrap; cursor:pointer; user-select:none;
transition:color .12s;
position: sticky; top: 0; z-index: 2; /* thead sticks to top of scroll area */
thead th:hovercolor:var(--text2)
thead th.sortedcolor:var(--cyan)
.simargin-left:3px;font-size:9px;opacity:.5
tbody trborder-bottom:1px solid var(--border);transition:background .08s
tbody tr:last-childborder-bottom:none
tbody tr:hoverbackground:rgba(34,211,238,.025)
tbody tdpadding:8px 13px;font-family:var(--mono);font-size:11px;white-space:nowrap
.tidcolor:var(--text3)
.tnamefont-family:var(--body);font-size:13px;font-weight:500;color:var(--text)
.tjob
display:inline-block; background:var(--bg2);
border:1px solid var(--border); padding:1px 6px;
font-size:9px; letter-spacing:.05em; border-radius:2px; color:var(--text2);
.tsalcolor:var(--green);font-weight:500
.tdatecolor:var(--text2)
.tnullcolor:var(--text3);font-style:italic;font-size:10px
.tpctcolor:var(--purple)
/* pagination — pinned to bottom of tbl-wrap, never scrolls away */
.pg
display:flex; align-items:center; justify-content:space-between;
padding:10px 14px; background:var(--bg2); border-top:1px solid var(--border);
flex-shrink: 0;
.pg-infofont-family:var(--mono);font-size:10px;color:var(--text2)
.pg-btnsdisplay:flex;gap:5px
.pg-btn
padding:4px 12px; background:var(--bg1);
border:1px solid var(--border); color:var(--text2);
font-family:var(--mono); font-size:10px; cursor:pointer;
transition:all .12s; border-radius:2px;
.pg-btn:hover:not(:disabled)border-color:var(--red);color:var(--red)
.pg-btn:disabledopacity:.25;cursor:not-allowed
/* states */
.loading-stdisplay:flex;align-items:center;justify-content:center;padding:64px;gap:12px;color:var(--text2);font-family:var(--mono);font-size:12px
.spinwidth:16px;height:16px;border:2px solid var(--border2);border-top-color:var(--red);border-radius:50%;animation:rotate .65s linear infinite
@keyframes rotatetotransform:rotate(360deg)
.error-stpadding:28px;background:rgba(201,66,54,.04);border:1px solid rgba(201,66,54,.18);border-radius:2px
.error-titlecolor:var(--red);font-family:var(--syne);font-size:13px;font-weight:700;margin-bottom:8px
.error-bodyfont-family:var(--mono);font-size:11px;color:var(--text2);line-height:1.6
.empty-stdisplay:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px;gap:6px;color:var(--text3)
.empty-icofont-size:28px;opacity:.3;margin-bottom:6px
.welcome-stdisplay:flex;flex-direction:column;align-items:center;padding:72px 40px;gap:14px;text-align:center
.welcome-ico
width:60px;height:60px;background:var(--bg2);border:1px solid var(--border);
display:flex;align-items:center;justify-content:center;font-size:26px;
margin-bottom:4px; border-radius:2px;
.welcome-hfont-family:var(--syne);font-size:19px;font-weight:800;color:var(--text)
.welcome-pcolor:var(--text2);max-width:420px;font-size:13px;line-height:1.6
.welcome-p codefont-family:var(--mono);font-size:11px;background:var(--bg2);padding:1px 5px;border-radius:2px;color:var(--cyan)
/* toast */
.toast
position:fixed;bottom:22px;right:22px;
background:var(--green);color:#fff;
padding:7px 16px;border-radius:2px;
font-family:var(--mono);font-size:11px;
z-index:9999;pointer-events:none;
transition:opacity .25s; opacity:0;
.toast.showopacity:1
</style>
</head>
<body>
<header class="header">
<div class="hb">
<div class="oracle-badge">Oracle ORDS</div>
<div>
<div class="header-title">AutoREST Query Filter Explorer</div>
<div class="header-sub">HR.EMPLOYEES · Dynamic <em>q=</em> Parameter Showcase</div>
</div>
</div>
<div class="endpoint-pill">
<span class="dot-live"></span>
localhost:8080/ords/hr/employees/
</div>
</header>
<div class="app">
<!-- ══════════════════════════════════════════════
SIDEBAR
══════════════════════════════════════════════ -->
<aside class="sidebar" id="sidebar">
<!-- Logic mode -->
<div>
<div class="sec-label">Top-level Logic</div>
<div class="fg">
<div class="toggle">
<button class="tgl-btn on" id="btn-and" onclick="setLogic('and')">AND</button>
<button class="tgl-btn" id="btn-or" onclick="setLogic('or')">OR</button>
</div>
<span class="help">AND = implicit object merge (default)<br>OR = $or:[…] array wrapper</span>
</div>
</div>
<!-- Identity -->
<div>
<div class="sec-label">Identity</div>
<div class="fg">
<div class="fr">
<div class="field">
<span class="fl">employee_id</span>
<input type="number" id="f-empid" placeholder="100–206" min="100" max="206">
</div>
<div class="field">
<span class="fl">emp_id op</span>
<select id="f-empop">
<option value="$eq">= eq</option>
<option value="$gt">> gt</option>
<option value="$gte">>= gte</option>
<option value="$lt">< lt</option>
<option value="$lte"><= lte</option>
</select>
</div>
</div>
<div class="fr">
<div class="field">
<span class="fl">department_id</span>
<input type="number" id="f-deptid" placeholder="e.g. 90">
</div>
<div class="field">
<span class="fl">manager_id</span>
<input type="number" id="f-mgrid" placeholder="e.g. 100">
</div>
</div>
</div>
</div>
<!-- Name -->
<div>
<div class="sec-label">Name Search</div>
<div class="fg">
<div class="field">
<span class="fl">first_name</span>
<select class="op-sel full-op" id="f-fnop">
<option value="$like">LIKE (wildcard)</option>
<option value="$eq">= eq (exact)</option>
<option value="$ne">≠ ne (not equal)</option>
</select>
<input type="text" id="f-fn" placeholder="%Steven%">
<span class="help">% is wildcard — e.g. %en% matches "Steven"</span>
</div>
<div class="field">
<span class="fl">last_name</span>
<select class="op-sel full-op" id="f-lnop">
<option value="$like">LIKE (wildcard)</option>
<option value="$eq">= eq (exact)</option>
<option value="$ne">≠ ne (not equal)</option>
</select>
<input type="text" id="f-ln" placeholder="K%">
</div>
</div>
</div>
<!-- Job -->
<div>
<div class="sec-label">Job</div>
<div class="fg">
<div class="field">
<span class="fl">job_id</span>
<select id="f-jobid">
<option value="">— any —</option>
<optgroup label="Accounting">
<option>AC_ACCOUNT</option><option>AC_MGR</option>
</optgroup>
<optgroup label="Administration">
<option>AD_ASST</option><option>AD_PRES</option><option>AD_VP</option>
</optgroup>
<optgroup label="Finance">
<option>FI_ACCOUNT</option><option>FI_MGR</option>
</optgroup>
<optgroup label="Human Resources">
<option>HR_REP</option>
</optgroup>
<optgroup label="IT">
<option>IT_PROG</option>
</optgroup>
<optgroup label="Marketing">
<option>MK_MAN</option><option>MK_REP</option>
</optgroup>
<optgroup label="Public Relations">
<option>PR_REP</option>
</optgroup>
<optgroup label="Purchasing">
<option>PU_CLERK</option><option>PU_MAN</option>
</optgroup>
<optgroup label="Sales">
<option>SA_MAN</option><option>SA_REP</option>
</optgroup>
<optgroup label="Shipping">
<option>SH_CLERK</option><option>ST_CLERK</option><option>ST_MAN</option>
</optgroup>
<option value="__PAT__">Custom LIKE pattern…</option>
</select>
</div>
<div class="field" id="job-pat-wrap" style="display:none">
<span class="fl">job_id LIKE pattern</span>
<input type="text" id="f-jobpat" placeholder="%CLERK">
<span class="help">e.g. %CLERK — matches all clerks</span>
</div>
</div>
</div>
<!-- Salary -->
<div>
<div class="sec-label">Salary</div>
<div class="fg">
<div class="fr">
<div class="field">
<span class="fl">min ($gte)</span>
<input type="number" id="f-salmin" placeholder="2100" min="2100" max="24000">
</div>
<div class="field">
<span class="fl">max ($lte)</span>
<input type="number" id="f-salmax" placeholder="24000" min="2100" max="24000">
</div>
</div>
<span class="help">Dataset range: $2,100 – $24,000</span>
</div>
</div>
<!-- Hire Date -->
<div>
<div class="sec-label">Hire Date</div>
<div class="fg">
<div class="field">
<span class="fl">hired after ($gte)</span>
<input type="date" id="f-hfrom" min="2011-01-13" max="2018-04-21">
</div>
<div class="field">
<span class="fl">hired before ($lte)</span>
<input type="date" id="f-hto" min="2011-01-13" max="2018-04-21">
</div>
<span class="help">Uses ORDS date wrapper: "$date":"ISO-Z"</span>
</div>
</div>
<!-- Commission -->
<div>
<div class="sec-label">Commission PCT</div>
<div class="comm-row">
<button class="comm-btn on" id="cb-all" onclick="setComm('all')">All</button>
<button class="comm-btn" id="cb-has" onclick="setComm('has')">Has ($notnull)</button>
<button class="comm-btn" id="cb-none" onclick="setComm('none')">None ($null)</button>
</div>
</div>
<!-- Display -->
<div>
<div class="sec-label">Display & Sort</div>
<div class="fg">
<div class="fr">
<div class="field">
<span class="fl">limit</span>
<select id="f-limit">
<option value="10">10</option>
<option value="25" selected>25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
<div class="field">
<span class="fl">order by</span>
<select id="f-orderby">
<option value="">— default —</option>
<option value="EMPLOYEE_ID">Employee ID</option>
<option value="LAST_NAME">Last Name</option>
<option value="SALARY">Salary</option>
<option value="HIRE_DATE">Hire Date</option>
<option value="DEPARTMENT_ID">Dept ID</option>
</select>
</div>
</div>
<div class="toggle">
<button class="tgl-btn on" id="btn-asc" onclick="setOrd('ASC')">ASC ↑</button>
<button class="tgl-btn" id="btn-desc" onclick="setOrd('DESC')">DESC ↓</button>
</div>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:6px">
<button class="btn-exec" onclick="runQuery()">▶ Execute Query</button>
<button class="btn-reset" onclick="resetAll()">↺ Reset All Filters</button>
</div>
</aside>
<div class="resizer" id="resizer"></div>
<!-- ══════════════════════════════════════════════
QUERY PREVIEW PANEL
══════════════════════════════════════════════ -->
<div class="qpanel">
<div class="qpanel-inner">
<div class="qblocks">
<div class="qblock cyan">
<div class="qbl">q= filter parameter (ORDS JSON grammar)</div>
<div class="qtext" id="qprev"> </div>
</div>
<div class="qblock amber">
<div class="qbl">Full ORDS Request URL</div>
<div class="utext" id="uprev"><span class="ub">http://localhost:8080/ords/hr/employees/</span></div>
</div>
<div class="badges" id="badges-row"></div>
</div>
<div class="qactions">
<button class="cbtn" onclick="copyQ()">⎘ Copy q=</button>
<button class="cbtn" onclick="copyURL()">⎘ Copy URL</button>
<button class="cbtn open" onclick="openURL()">↗ Open URL</button>
</div>
</div>
</div>
<!-- ══════════════════════════════════════════════
MAIN RESULTS
══════════════════════════════════════════════ -->
<main class="main">
<div class="results-bar">
<div class="count-pill">
<span class="count-num" id="rcount">—</span>
<span>rows returned</span>
</div>
<span class="page-loc" id="ploc"></span>
</div>
<div class="tbl-wrap" id="tbl-wrap">
<div class="welcome-st">
<div class="welcome-ico">⚡</div>
<div class="welcome-h">ORDS AutoREST Query Filter Explorer</div>
<p class="welcome-p">
Build a filter using the controls on the left, then click <strong>Execute Query</strong>.<br>
Watch the <code>q=</code> parameter assemble in real time — showcasing how a single
AutoREST endpoint handles every query shape without any custom SQL.
</p>
</div>
</div>
</main>
</div>
<div class="toast" id="toast"></div>
<script>
// ─────────────────────────────────────────────
// STATE
// ─────────────────────────────────────────────
const BASE = 'http://localhost:8080/ords/hr/employees/';
const S =
logic: 'and',
ord: 'ASC',
comm: 'all',
offset: 0
;
// ─────────────────────────────────────────────
// WIRE UP LIVE PREVIEW
// ─────────────────────────────────────────────
document.querySelectorAll('input, select').forEach(el =>
el.addEventListener('input', livePreview);
el.addEventListener('change', livePreview);
);
document.getElementById('f-jobid').addEventListener('change', function()
const wrap = document.getElementById('job-pat-wrap');
wrap.style.display = this.value === '__PAT__' ? 'flex' : 'none';
if (this.value === '__PAT__') wrap.style.flexDirection = 'column';
);
// ─────────────────────────────────────────────
// TOGGLE HELPERS
// ─────────────────────────────────────────────
function setLogic(v)
S.logic = v;
q('btn-and').classList.toggle('on', v==='and');
q('btn-or').classList.toggle('on', v==='or');
livePreview();
function setOrd(v)
S.ord = v;
q('btn-asc').classList.toggle('on', v==='ASC');
q('btn-desc').classList.toggle('on', v==='DESC');
livePreview();
function setComm(v)
S.comm = v;
['all','has','none'].forEach(id => q('cb-'+id).classList.toggle('on', id===v));
livePreview();
function q(id) return document.getElementById(id)
function val(id) return q(id).value.trim()
// ─────────────────────────────────────────────
// FILTER BUILDER
// ─────────────────────────────────────────────
function collectFilters()
const fs = [];
const empid = val('f-empid');
if (empid) fs.push( col:'employee_id', op:val('f-empop'), v:+empid, lbl:`employee_id $val('f-empop') $empid` );
const deptid = val('f-deptid');
if (deptid) fs.push( col:'department_id', op:'$eq', v:+deptid, lbl:`department_id = $deptid` );
const mgrid = val('f-mgrid');
if (mgrid) fs.push( col:'manager_id', op:'$eq', v:+mgrid, lbl:`manager_id = $mgrid` );
const fn = val('f-fn');
if (fn) fs.push( col:'first_name', op:val('f-fnop'), v:fn, lbl:`first_name $val('f-fnop') "$fn"` );
const ln = val('f-ln');
if (ln) fs.push( col:'last_name', op:val('f-lnop'), v:ln, lbl:`last_name $val('f-lnop') "$ln"` );
const jobid = val('f-jobid');
if (jobid && jobid !== '__PAT__')
fs.push( col:'job_id', op:'$eq', v:jobid, lbl:`job_id = "$jobid"` );
else if (jobid === '__PAT__')
const pat = val('f-jobpat');
if (pat) fs.push( col:'job_id', op:'$like', v:pat, lbl:`job_id LIKE "$pat"` );
const salmin = val('f-salmin');
if (salmin) fs.push( col:'salary', op:'$gte', v:+salmin, lbl:`salary >= $salmin` );
const salmax = val('f-salmax');
if (salmax) fs.push( col:'salary', op:'$lte', v:+salmax, lbl:`salary <= $salmax` );
const hfrom = val('f-hfrom');
if (hfrom) fs.push( col:'hire_date', op:'$gte', v: $date: hfrom+'T00:00:00Z' , lbl:`hire_date >= $hfrom`, isDate:true );
const hto = val('f-hto');
if (hto) fs.push( col:'hire_date', op:'$lte', v: $date: hto+'T23:59:59Z' , lbl:`hire_date <= $hto`, isDate:true );
if (S.comm === 'has') fs.push( col:'commission_pct', op:'$notnull', v:null, lbl:'commission_pct IS NOT NULL', isNull:true );
if (S.comm === 'none') fs.push( col:'commission_pct', op:'$null', v:null, lbl:'commission_pct IS NULL', isNull:true );
return fs;
function buildQ(fs)
const orderby = val('f-orderby');
// Build atomic predicate objects
function pred(f)
if (f.op === '$null') return [f.col]: $null: null ;
if (f.op === '$notnull') return [f.col]: $notnull: null ;
return [f.col]: [f.op]: f.v ;
// $orderby clause — lives inside q= JSON per ORDS grammar
const orderClause = orderby
? $orderby: [orderby]: S.ord.toLowerCase()
: null;
if (!fs.length)
const preds = fs.map(pred);
let qObj;
if (S.logic === 'or')
qObj = $or: preds ;
else
// AND: if any column is repeated we must use explicit $and array
const cols = fs.map(f => f.col);
const hasDup = cols.some((c,i) => cols.indexOf(c) !== i);
qObj = hasDup ? $and: preds : Object.assign(, ...preds);
// Merge $orderby into the top-level q object
if (orderClause) Object.assign(qObj, orderClause);
return qObj;
// ─────────────────────────────────────────────
// PARAMS & URL
// ─────────────────────────────────────────────
function buildParams(qObj, offset) 25;
if (qObj) ps.push(['q', JSON.stringify(qObj)]);
ps.push(['limit', limit]);
if (offset > 0) ps.push(['offset', offset]);
// $orderby lives inside q= per ORDS grammar — no separate URL params needed
return ps;
function buildURL(qObj, offset)
const ps = buildParams(qObj, offset);
if (!ps.length) return BASE;
return BASE + '?' + ps.map(([k,v]) => `$k=$encodeURIComponent(v)`).join('&');
// ─────────────────────────────────────────────
// LIVE PREVIEW
// ─────────────────────────────────────────────
function livePreview()
const fs = collectFilters();
const qObj = buildQ(fs);
const pretty = qObj ? JSON.stringify(qObj, null, 2) : ' ';
q('qprev').textContent = pretty;
// URL with colored spans
const ps = buildParams(qObj, 0);
if (ps.length <= 1 && !qObj)
q('uprev').innerHTML = `<span class="ub">$BASE</span>`;
else
let html = `<span class="ub">$BASE?</span>`;
html += ps.map(([k,v]) =>
`<span class="up">$k</span><span class="ub">=</span><span class="uv">$encodeURIComponent(v)</span>`
).join('<span class="ub">&</span>');
q('uprev').innerHTML = html;
// Active filter badges
q('badges-row').innerHTML = fs.map(f =>
`<span class="badge"><span class="bc">$f.col</span><span class="bo"> $f.op.replace('$','') </span><span class="bv">$f.isNull ? 'NULL' : (f.isDate ? f.lbl.split(/[<>]=? /)[1] : String(f.v).substring(0,24))</span></span>`
).join('');
// ─────────────────────────────────────────────
// QUERY EXECUTION
// ─────────────────────────────────────────────
async function runQuery(offset=0)
function renderTable(data, offset, limit)
function clickSort(col)
const current = val('f-orderby');
const colUC = col.toUpperCase();
if (current === colUC)
setOrd(S.ord==='ASC'?'DESC':'ASC');
else
q('f-orderby').value = colUC;
setOrd('ASC');
runQuery(0);
// ─────────────────────────────────────────────
// COPY / OPEN
// ─────────────────────────────────────────────
function copyQ()
const qObj = buildQ(collectFilters());
navigator.clipboard.writeText(qObj ? JSON.stringify(qObj) : '').then(()=>toast('q= parameter copied!'));
function copyURL()
navigator.clipboard.writeText(buildURL(buildQ(collectFilters()),0)).then(()=>toast('URL copied!'));
function openURL()
window.open(buildURL(buildQ(collectFilters()),0), '_blank');
// ─────────────────────────────────────────────
// RESET
// ─────────────────────────────────────────────
function resetAll()
['f-empid','f-deptid','f-mgrid','f-fn','f-ln','f-jobpat','f-salmin','f-salmax','f-hfrom','f-hto']
.forEach(id => q(id).value = '');
q('f-empop').value = '$eq';
q('f-fnop').value = '$like';
q('f-lnop').value = '$like';
q('f-jobid').value = '';
q('f-limit').value = '25';
q('f-orderby').value = '';
q('job-pat-wrap').style.display = 'none';
setLogic('and');
setOrd('ASC');
setComm('all');
livePreview();
// ─────────────────────────────────────────────
// UTILS
// ─────────────────────────────────────────────
function fmtDate(v)
if (!v) return '<span class="tnull">—</span>';
try
const d = new Date(v);
return d.toLocaleDateString('en-US',year:'numeric',month:'short',day:'numeric');
catch return v;
function escHtml(s)
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
let toastTimer;
function toast(msg)
const t = q('toast');
t.textContent = msg;
t.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(()=>t.classList.remove('show'), 2200);
// ─────────────────────────────────────────────
// SIDEBAR RESIZE
// ─────────────────────────────────────────────
(function()
const resizer = document.getElementById('resizer');
const app = resizer.closest('.app');
const sidebar = document.getElementById('sidebar');
const MIN_W = 220;
const MAX_W = 600;
let dragging = false;
let startX, startW;
resizer.addEventListener('mousedown', e =>
dragging = true;
startX = e.clientX;
startW = sidebar.getBoundingClientRect().width;
resizer.classList.add('dragging');
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
e.preventDefault();
);
document.addEventListener('mousemove', e =>
if (!dragging) return;
const newW = Math.min(MAX_W, Math.max(MIN_W, startW + (e.clientX - startX)));
app.style.setProperty('--sidebar-w', newW + 'px');
);
document.addEventListener('mouseup', () =>
if (!dragging) return;
dragging = false;
resizer.classList.remove('dragging');
document.body.style.cursor = '';
document.body.style.userSelect = '';
);
)();
// ─────────────────────────────────────────────
// INIT
// ─────────────────────────────────────────────
livePreview();
</script>
</body>
</html>
People would think I’ve done it all, but no. We have some cool things cooking in version 26.1 which will be released in a few weeks. Think… VECTOR SEARCH.
PakarPBN
A Private Blog Network (PBN) is a collection of websites that are controlled by a single individual or organization and used primarily to build backlinks to a “money site” in order to influence its ranking in search engines such as Google. The core idea behind a PBN is based on the importance of backlinks in Google’s ranking algorithm. Since Google views backlinks as signals of authority and trust, some website owners attempt to artificially create these signals through a controlled network of sites.
In a typical PBN setup, the owner acquires expired or aged domains that already have existing authority, backlinks, and history. These domains are rebuilt with new content and hosted separately, often using different IP addresses, hosting providers, themes, and ownership details to make them appear unrelated. Within the content published on these sites, links are strategically placed that point to the main website the owner wants to rank higher. By doing this, the owner attempts to pass link equity (also known as “link juice”) from the PBN sites to the target website.
The purpose of a PBN is to give the impression that the target website is naturally earning links from multiple independent sources. If done effectively, this can temporarily improve keyword rankings, increase organic visibility, and drive more traffic from search results.