-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexample-nginx.conf
More file actions
788 lines (685 loc) · 31.1 KB
/
Copy pathexample-nginx.conf
File metadata and controls
788 lines (685 loc) · 31.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
# Reference examples for ngx_http_sqlite_module
# This file is a reference. Copy selected locations into an Nginx http/server
# context and adjust paths/tokens for your deployment
#
# Prefer migrations for durable schema changes
# scripts/migrate-sqlite.sh ./data/example.sqlite ./examples/reference/migrations/sqlite
# scripts/migrate-sqlite.sh status ./data/example.sqlite ./examples/reference/migrations/sqlite
# scripts/migrate-sqlite.sh verify ./data/example.sqlite ./examples/reference/migrations/sqlite
server {
listen 8080;
server_name localhost;
root /usr/local/share/ngx_http_sqlite_module/examples/reference/www;
index index.html;
# Locations inherit sqlite_db unless they set their own value
sqlite_db "/var/lib/ngx_http_sqlite_module/example.sqlite";
sqlite_bearer_token "change-me";
sqlite_response json;
sqlite_max_body_size 1m;
sqlite_require_json_content_type on;
sqlite_meta_headers off;
sqlite_error_log "/var/lib/ngx_http_sqlite_module/sqlite-errors.jsonl";
sqlite_busy_timeout 3000ms;
sqlite_query_timeout 5s;
sqlite_max_rows 10000;
sqlite_connections 2;
sqlite_execution thread_pool;
sqlite_thread_pool default;
sqlite_db_mode read_write;
sqlite_journal_mode auto;
sqlite_synchronous normal;
sqlite_temp_store memory;
sqlite_foreign_keys on;
sqlite_authorizer on;
sqlite_warmup on;
sqlite_warmup_strict on;
sqlite_validate on;
# Mutating example routes inherit this bearer token; add
# -H 'Authorization: Bearer change-me'
# to the mutating curl snippets below. Read-only routes opt into
# sqlite_allow_anon on
location / {
try_files $uri $uri/ =404;
}
location = /examples/bootstrap {
sqlite;
sqlite_operation status;
sqlite_method GET;
sqlite_sql "SELECT 'ready' AS status";
sqlite_read_only on;
sqlite_allow_anon on;
}
# JSON-backed create/update
# curl -X POST http://localhost:8080/examples/devices/json \
# -H 'Authorization: Bearer change-me' \
# -H 'Content-Type: application/json' \
# -d '{"id":"device-1","name":"Sensor","attrs":{"room":"lab"}}'
location = /examples/devices/json {
sqlite;
sqlite_max_body_size 64k;
sqlite_operation upsert_device_json;
sqlite_method POST PUT PATCH;
sqlite_sql "INSERT INTO devices (id, name, attrs, created_at, updated_at) VALUES (:id, :name, json(CASE WHEN :attrs = '' THEN '{}' ELSE :attrs END), strftime('%s','now'), strftime('%s','now')) ON CONFLICT(id) DO UPDATE SET name = excluded.name, attrs = excluded.attrs, updated_at = excluded.updated_at WHERE devices.name IS NOT excluded.name OR devices.attrs IS NOT excluded.attrs";
sqlite_param id json:id required raw;
sqlite_param name json:$.name required raw;
sqlite_param attrs json:$.attrs "default:{}" raw;
sqlite_response status_only;
sqlite_success_status 204;
sqlite_invalid_json_status 422;
}
# Store an encoded Argon2id token hash from a JSON body
location = /examples/devices/token {
sqlite;
sqlite_max_body_size 16k;
sqlite_operation set_token;
sqlite_method POST PUT;
sqlite_sql "UPDATE devices SET token_hash = :token_hash, updated_at = strftime('%s','now') WHERE id = :id";
sqlite_param id json:id required raw;
sqlite_param token_hash json:token required argon2id;
sqlite_require_changes on;
sqlite_response status_only;
sqlite_success_status 204;
sqlite_empty_status 404;
sqlite_invalid_json_status 422;
}
# Verify a token against its stored Argon2id hash without returning the hash
location = /examples/devices/token/check {
sqlite;
sqlite_max_body_size 16k;
sqlite_operation check_token;
sqlite_method POST;
sqlite_sql "SELECT id, 1 AS valid FROM devices WHERE id = :id AND sqlite_argon2id_verify(token_hash, :token) = 1";
sqlite_param id json:id required raw;
sqlite_param token json:token required raw;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_empty_status 401;
sqlite_invalid_json_status 422;
}
# Query-arg detail route with a JSON object response
# curl 'http://localhost:8080/examples/devices/detail?id=device-1'
location = /examples/devices/detail {
sqlite;
sqlite_operation get_device;
sqlite_method GET;
sqlite_sql "SELECT id, name, json(attrs) AS attrs, created_at AS createdAt, updated_at AS updatedAt FROM devices WHERE id = :id";
sqlite_param id arg:id required raw;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_json_value attrs;
sqlite_empty_status 404;
}
# Query-arg list route with JSON array response, pagination helpers, and SQL aliases
# curl 'http://localhost:8080/examples/devices/list?limit=50&offset=0'
location = /examples/devices/list {
sqlite;
sqlite_operation list_devices;
sqlite_method GET;
sqlite_sql "SELECT id, name, json(attrs) AS attrs, created_at AS createdAt, updated_at AS updatedAt FROM devices ORDER BY updated_at DESC LIMIT :limit OFFSET :offset";
sqlite_param limit arg:limit default:50 int_positive;
sqlite_param offset arg:offset default:0 int_nonnegative;
sqlite_response json_rows;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_json_value attrs;
}
# Line-delimited JSON for incremental consumers and data pipelines
location = /examples/devices/list.ndjson {
sqlite;
sqlite_meta_headers on;
sqlite_operation list_devices_ndjson;
sqlite_method GET;
sqlite_sql "SELECT id, name, json(attrs) AS attrs, created_at AS createdAt, updated_at AS updatedAt FROM devices ORDER BY updated_at DESC LIMIT :limit OFFSET :offset";
sqlite_param limit arg:limit default:50 int_positive max:1000;
sqlite_param offset arg:offset default:0 int_nonnegative;
sqlite_response ndjson;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_json_value attrs;
sqlite_header X-Content-Format ndjson;
}
# RFC 4180-style CSV with a header row and base64-encoded BLOB cells
location = /examples/devices/list.csv {
sqlite;
sqlite_meta_headers on;
sqlite_operation list_devices_csv;
sqlite_method GET;
sqlite_sql "SELECT id, name, json(attrs) AS attrs, created_at AS createdAt, updated_at AS updatedAt FROM devices ORDER BY updated_at DESC LIMIT :limit OFFSET :offset";
sqlite_param limit arg:limit default:50 int_positive max:1000;
sqlite_param offset arg:offset default:0 int_nonnegative;
sqlite_response csv;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_header Content-Disposition "inline; filename=devices.csv";
}
# Third-party hook read route using an opted-in configured operation
# curl 'http://localhost:8080/examples/hooks/devices/detail?id=device-1'
location = /examples/hooks/devices/detail {
sqlite_operation hook_get_device;
sqlite_method GET;
sqlite_sql "SELECT id, name, json(attrs) AS attrs, created_at AS createdAt, updated_at AS updatedAt FROM devices WHERE id = :id";
sqlite_param id arg:id required raw;
sqlite_read_only on;
sqlite_json_value attrs;
sqlite_empty_status 404;
sqlite_hookable on;
sqlite_hook_example hook_get_device;
sqlite_hook_example_param id id;
}
# Third-party hook write/read route with two operations in one request
# curl 'http://localhost:8080/examples/hooks/devices/touch?id=device-1&name=Hooked'
location = /examples/hooks/devices/touch {
sqlite_operation hook_touch_device;
sqlite_method GET;
sqlite_sql "UPDATE devices SET name = :name, updated_at = strftime('%s','now') WHERE id = :id";
sqlite_param id arg:id required raw;
sqlite_param name arg:name required raw;
sqlite_response status_only;
sqlite_success_status 204;
sqlite_require_changes on;
sqlite_hookable on;
sqlite_operation hook_read_touched_device;
sqlite_method GET;
sqlite_sql "SELECT id, name, json(attrs) AS attrs, created_at AS createdAt, updated_at AS updatedAt FROM devices WHERE id = :id";
sqlite_param id arg:id required raw;
sqlite_response json;
sqlite_read_only on;
sqlite_json_value attrs;
sqlite_empty_status 404;
sqlite_hookable on;
sqlite_hook_example hook_touch_device;
sqlite_hook_example hook_read_touched_device;
sqlite_hook_example_param id id;
sqlite_hook_example_param name name;
}
# DELETE route with status-only response
# curl -X DELETE 'http://localhost:8080/examples/devices?id=device-1'
location = /examples/devices {
sqlite;
sqlite_operation delete_device;
sqlite_method DELETE;
sqlite_sql "DELETE FROM devices WHERE id = :id";
sqlite_param id arg:id required raw;
sqlite_require_changes on;
sqlite_response status_only;
sqlite_success_status 204;
sqlite_empty_status 404;
}
# Project flag system showing JSON writes, bool/int transforms, and raw JSON columns
# curl -X POST http://localhost:8080/examples/projects/flags \
# -H 'Authorization: Bearer change-me' \
# -H 'Content-Type: application/json' \
# -d '{"projectId":"project-1","key":"checkout_v2","enabled":true,"rollout":100,"rules":{"plans":["pro"]}}'
location = /examples/projects/flags {
sqlite;
sqlite_max_body_size 32k;
sqlite_operation upsert_project_flag;
sqlite_method POST PUT PATCH;
sqlite_sql "INSERT INTO project_flags (project_id, flag_key, enabled, rollout, rules, created_at, updated_at) VALUES (:project_id, :flag_key, :enabled, CASE WHEN :rollout > 100 THEN 100 ELSE :rollout END, json(CASE WHEN :rules = '' THEN '{}' ELSE :rules END), strftime('%s','now'), strftime('%s','now')) ON CONFLICT(project_id, flag_key) DO UPDATE SET enabled = excluded.enabled, rollout = excluded.rollout, rules = excluded.rules, updated_at = excluded.updated_at RETURNING project_id AS projectId, flag_key AS flagKey, enabled, rollout, json(rules) AS rules, created_at AS createdAt, updated_at AS updatedAt";
sqlite_param project_id json:projectId required raw;
sqlite_param flag_key json:key required raw;
sqlite_param enabled json:enabled default:false bool_int;
sqlite_param rollout json:rollout default:0 int_nonnegative;
sqlite_param rules json:rules "default:{}" raw;
sqlite_json_value rules;
sqlite_invalid_json_status 422;
sqlite_operation delete_project_flag;
sqlite_method DELETE;
sqlite_sql "DELETE FROM project_flags WHERE project_id = :project_id AND flag_key = :flag_key";
sqlite_param project_id arg:projectId required raw;
sqlite_param flag_key arg:key required raw;
sqlite_require_changes on;
sqlite_response status_only;
sqlite_success_status 204;
sqlite_empty_status 404;
}
# Public flag list route with pagination
# curl 'http://localhost:8080/examples/projects/flags/list?projectId=project-1'
location = /examples/projects/flags/list {
sqlite;
sqlite_operation list_project_flags;
sqlite_method GET;
sqlite_sql "SELECT project_id AS projectId, flag_key AS flagKey, enabled, rollout, json(rules) AS rules, created_at AS createdAt, updated_at AS updatedAt FROM project_flags WHERE project_id = :project_id ORDER BY updated_at DESC LIMIT :limit OFFSET :offset";
sqlite_param project_id arg:projectId required raw;
sqlite_param limit arg:limit default:50 int_positive;
sqlite_param offset arg:offset default:0 int_nonnegative;
sqlite_response json_rows;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_json_value rules;
}
# Public flag evaluation route with deterministic SQL-only bucketing
# curl 'http://localhost:8080/examples/projects/flags/evaluate?projectId=project-1&key=checkout_v2&subject=user-1'
location = /examples/projects/flags/evaluate {
sqlite;
sqlite_operation evaluate_project_flag;
sqlite_method GET;
sqlite_sql "SELECT project_id AS projectId, flag_key AS flagKey, enabled, rollout, ((length(:subject) * 31 + length(flag_key) * 17 + length(project_id) * 13) % 100) AS bucket, CASE WHEN enabled = 1 AND rollout >= 100 THEN 1 WHEN enabled = 1 AND ((length(:subject) * 31 + length(flag_key) * 17 + length(project_id) * 13) % 100) < rollout THEN 1 ELSE 0 END AS allowed, json(rules) AS rules FROM project_flags WHERE project_id = :project_id AND flag_key = :flag_key";
sqlite_param project_id arg:projectId required raw;
sqlite_param flag_key arg:key required raw;
sqlite_param subject arg:subject default:anonymous raw;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_json_value rules;
sqlite_empty_status 404;
}
# Multi-statement write/read transaction with the response taken from the
# last result-producing statement
# curl -X POST http://localhost:8080/examples/batch \
# -H 'Authorization: Bearer change-me' \
# -H 'Content-Type: application/json' \
# -d '{"id":"batch-1","label":"Batch demo"}'
location = /examples/batch {
sqlite;
sqlite_max_body_size 32k;
sqlite_operation upsert_batch;
sqlite_method POST PUT PATCH;
sqlite_batch "
BEGIN;
INSERT INTO batch_tests (id, label, updated_at)
VALUES (:id, :label, strftime('%s','now'))
ON CONFLICT(id) DO UPDATE SET
label = excluded.label,
updated_at = excluded.updated_at
WHERE batch_tests.label IS NOT excluded.label;
SELECT id, label, updated_at AS updatedAt FROM batch_tests WHERE id = :id;
COMMIT;
";
sqlite_param id json:id required raw;
sqlite_param label json:label required raw;
sqlite_invalid_json_status 422;
}
# Third-party hook batch route using the multiline batch executor
# curl 'http://localhost:8080/examples/hooks/batch?id=batch-1&label=Hooked'
location = /examples/hooks/batch {
sqlite_operation hook_upsert_batch;
sqlite_method GET;
sqlite_batch "
BEGIN;
INSERT INTO batch_tests (id, label, updated_at)
VALUES (:id, :label, strftime('%s','now'))
ON CONFLICT(id) DO UPDATE SET
label = excluded.label,
updated_at = excluded.updated_at
WHERE batch_tests.label IS NOT excluded.label;
SELECT id, label, updated_at AS updatedAt FROM batch_tests WHERE id = :id;
COMMIT;
";
sqlite_param id arg:id required raw;
sqlite_param label arg:label required raw;
sqlite_hookable on;
sqlite_hook_example hook_upsert_batch;
sqlite_hook_example_param id id;
sqlite_hook_example_param label label;
}
# Every non-body parameter source plus null/default/length/enum validation
# curl 'http://localhost:8080/examples/params/sources?q=alpha' \
# -H 'X-Request-ID: 6ba7b810-9dad-41d1-80b4-00c04fd430c8' \
# -H 'Cookie: session=12345678'
location = /examples/params/sources {
sqlite;
sqlite_operation parameter_sources;
sqlite_method GET;
sqlite_sql "SELECT :query AS queryValue, :request_id AS requestId, :session AS session, :method AS method, :optional IS NULL AS optionalWasNull, :empty_optional = '' AS optionalWasEmpty, :defaulted AS defaulted";
sqlite_param query arg:q required raw min_length:2 max_length:16 enum:alpha,beta;
sqlite_param request_id header:X-Request-ID required uuid;
sqlite_param session cookie:session required raw min_length:8 max_length:32;
sqlite_param method var:request_method required enum:GET;
sqlite_param optional arg:optional null raw;
sqlite_param empty_optional arg:empty optional raw;
sqlite_param defaulted arg:defaulted default:demo raw;
sqlite_read_only on;
sqlite_allow_anon on;
}
# JSON parameters demonstrate numeric/bool/UUID/base64 transforms, range
# and enum validation, JSON-value output, a BLOB response, and HTTP 201.
location = /examples/params/typed {
sqlite;
sqlite_max_body_size 4k;
sqlite_operation typed_parameters;
sqlite_method POST;
sqlite_sql "INSERT INTO feature_samples (id, label, positive_value, nonnegative_value, int64_value, real_value, enabled, role, request_uuid, payload, metadata, optional_text, created_at) VALUES (:id, :label, :positive_value, :nonnegative_value, :int64_value, :real_value, :enabled, :role, :request_uuid, :payload, :metadata, :optional_text, strftime('%s','now')) ON CONFLICT(id) DO UPDATE SET label = excluded.label, positive_value = excluded.positive_value, nonnegative_value = excluded.nonnegative_value, int64_value = excluded.int64_value, real_value = excluded.real_value, enabled = excluded.enabled, role = excluded.role, request_uuid = excluded.request_uuid, payload = excluded.payload, metadata = excluded.metadata, optional_text = excluded.optional_text RETURNING id, label, positive_value AS positiveValue, nonnegative_value AS nonnegativeValue, int64_value AS int64Value, typeof(int64_value) AS int64Type, real_value AS realValue, typeof(real_value) AS realType, enabled, role, request_uuid AS requestId, payload, json(metadata) AS metadata, optional_text AS optionalText";
sqlite_param id json:id required uuid;
sqlite_param label json:label required raw min_length:3 max_length:32;
sqlite_param positive_value json:positive required int_positive min:1 max:100;
sqlite_param nonnegative_value json:nonnegative required int_nonnegative min:0 max:100;
sqlite_param int64_value json:int64 required int64;
sqlite_param real_value json:real required real;
sqlite_param enabled json:enabled required bool_int;
sqlite_param role json:role required raw enum:admin,viewer;
sqlite_param request_uuid header:X-Request-ID required uuid;
sqlite_param payload json:payload required base64;
sqlite_param metadata json:metadata "default:{}" raw;
sqlite_param optional_text json:optionalText null raw;
sqlite_json_value metadata;
sqlite_header X-Example-Feature typed-parameters;
sqlite_success_status 201;
sqlite_invalid_json_status 422;
}
# Native preserves JSON integer, real, boolean, string, and null types.
location = /examples/params/native {
sqlite;
sqlite_operation native_parameter;
sqlite_method POST;
sqlite_sql "SELECT typeof(:value) AS sqliteType, :value AS value";
sqlite_param value json:value required native;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_invalid_json_status 422;
}
# Compatibility route for clients that send JSON with text/plain.
location = /examples/content-type/legacy {
sqlite;
sqlite_require_json_content_type off;
sqlite_operation legacy_content_type;
sqlite_method POST;
sqlite_sql "SELECT :value AS value";
sqlite_param value json:value required raw;
sqlite_read_only on;
sqlite_allow_anon on;
}
# Prefer file-backed bearer tokens in deployed configurations.
location = /examples/auth/file {
sqlite;
sqlite_bearer_token_file "/var/lib/ngx_http_sqlite_module/example-bearer-token";
sqlite_operation file_token_auth;
sqlite_method GET;
sqlite_sql "SELECT 'authenticated from token file' AS message";
sqlite_read_only on;
}
# Envelope, custom header, metadata headers, JSON rows, and Nginx variables.
location = /examples/responses/envelope {
sqlite;
sqlite_meta_headers on;
sqlite_operation envelope_response;
sqlite_method GET;
sqlite_sql "SELECT 'first' AS name UNION ALL SELECT 'second' AS name";
sqlite_response json_rows;
sqlite_envelope on;
sqlite_header X-Example-Feature envelope;
sqlite_read_only on;
sqlite_allow_anon on;
add_header X-SQLite-Variables "$sqlite_operation:$sqlite_rows:$sqlite_status:$sqlite_error_code" always;
}
location = /examples/responses/unlimited {
sqlite;
sqlite_operation unlimited_response;
sqlite_method GET;
sqlite_sql "SELECT hex(zeroblob(128)) AS value";
sqlite_max_response_size off;
sqlite_read_only on;
sqlite_allow_anon on;
}
# One location owns every supported HTTP method without duplicate mappings.
location = /examples/methods {
sqlite;
sqlite_operation methods_read;
sqlite_method GET HEAD;
sqlite_sql "SELECT 'GET_OR_HEAD' AS selected";
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_operation methods_post;
sqlite_method POST;
sqlite_sql "SELECT 'POST' AS selected";
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_operation methods_put;
sqlite_method PUT;
sqlite_sql "SELECT 'PUT' AS selected";
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_operation methods_patch;
sqlite_method PATCH;
sqlite_sql "SELECT 'PATCH' AS selected";
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_operation methods_delete;
sqlite_method DELETE;
sqlite_sql "SELECT 'DELETE' AS selected";
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_operation methods_options;
sqlite_method OPTIONS;
sqlite_sql "SELECT 'OPTIONS' AS selected";
sqlite_read_only on;
sqlite_allow_anon on;
}
# The entrypoint creates this DB and removes all write permissions.
location = /examples/policies/read-only-db {
sqlite;
sqlite_db "/var/lib/ngx_http_sqlite_module/read-only.sqlite";
sqlite_db_mode read_only;
sqlite_journal_mode auto;
sqlite_synchronous normal;
sqlite_temp_store memory;
sqlite_operation read_only_database;
sqlite_method GET;
sqlite_sql "SELECT id, message FROM read_only_info";
sqlite_read_only on;
sqlite_allow_anon on;
}
# A separate DB demonstrates create mode and alternate SQLite policies.
location = /examples/policies/create-db {
sqlite;
sqlite_db "/var/lib/ngx_http_sqlite_module/create-mode.sqlite";
sqlite_db_mode create;
sqlite_journal_mode delete;
sqlite_synchronous full;
sqlite_temp_store file;
sqlite_foreign_keys off;
sqlite_authorizer on;
sqlite_operation create_mode_database;
sqlite_method POST;
sqlite_sql "CREATE TABLE IF NOT EXISTS created_by_example (id integer PRIMARY KEY, message text)";
sqlite_response status_only;
sqlite_success_status 204;
}
# This is intentionally limited to a harmless PRAGMA. Keep the authorizer
# enabled for normal HTTP-facing operations.
location = /examples/policies/authorizer-off {
sqlite;
sqlite_authorizer off;
sqlite_operation authorizer_disabled;
sqlite_method GET;
sqlite_sql "PRAGMA user_version";
sqlite_allow_anon on;
}
# Positional parameters are normally rejected. With validation disabled the
# unbound value remains SQL NULL, shown here only as a compatibility example.
location = /examples/policies/validation-off {
sqlite;
sqlite_validate off;
sqlite_operation relaxed_validation;
sqlite_method GET;
sqlite_sql "SELECT ?1 IS NULL AS positionalWasNull";
sqlite_read_only on;
sqlite_allow_anon on;
}
# Synchronous execution plus deliberate lazy, non-strict initialization.
location = /examples/execution/sync {
sqlite;
sqlite_execution sync;
sqlite_connections 1;
sqlite_warmup off;
sqlite_warmup_strict off;
sqlite_operation synchronous_execution;
sqlite_method GET;
sqlite_sql "SELECT 'sync' AS execution";
sqlite_read_only on;
sqlite_allow_anon on;
}
location = /examples/execution/named-pool {
sqlite;
sqlite_execution thread_pool;
sqlite_thread_pool sqlite_examples;
sqlite_connections 1;
sqlite_operation named_thread_pool;
sqlite_method GET;
sqlite_sql "SELECT 'sqlite_examples' AS threadPool";
sqlite_read_only on;
sqlite_allow_anon on;
}
# A disconnected GET is cancelled and releases this single lane for POST.
location = /examples/execution/cancel {
sqlite;
sqlite_connections 1;
sqlite_operation cancelled_long_query;
sqlite_method GET;
sqlite_sql "WITH RECURSIVE counter(x) AS (VALUES(0) UNION ALL SELECT x + 1 FROM counter WHERE x < 100000000) SELECT sum(x) AS total FROM counter";
sqlite_query_timeout 0;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_operation after_cancel;
sqlite_method POST;
sqlite_sql "SELECT 'lane released' AS status";
sqlite_read_only on;
sqlite_allow_anon on;
}
# Two lanes let a quick POST proceed while a long GET occupies the other.
location = /examples/execution/lanes {
sqlite;
sqlite_connections 2;
sqlite_operation lane_long_query;
sqlite_method GET;
sqlite_sql "WITH RECURSIVE counter(x) AS (VALUES(0) UNION ALL SELECT x + 1 FROM counter WHERE x < 100000000) SELECT sum(x) AS total FROM counter";
sqlite_query_timeout 800ms;
sqlite_read_only on;
sqlite_allow_anon on;
sqlite_operation lane_quick_query;
sqlite_method POST;
sqlite_sql "SELECT 'second lane' AS status";
sqlite_read_only on;
sqlite_allow_anon on;
}
# Auto wraps the statements because there is no explicit transaction.
location = /examples/batch/auto {
sqlite;
sqlite_operation automatic_batch_transaction;
sqlite_method POST;
sqlite_batch "
INSERT INTO batch_tests (id, label, updated_at)
VALUES (:id, :label, strftime('%s','now'))
ON CONFLICT(id) DO UPDATE SET label = excluded.label, updated_at = excluded.updated_at
WHERE batch_tests.label IS NOT excluded.label;
SELECT id, label, updated_at AS updatedAt FROM batch_tests WHERE id = :id;
";
sqlite_batch_transaction auto;
sqlite_param id json:id required raw;
sqlite_param label json:label required raw;
}
# The SQL is loaded at Nginx config time and always receives an atomic wrapper.
location = /examples/batch/file {
sqlite;
sqlite_operation batch_from_file;
sqlite_method POST;
sqlite_batch_file "/usr/local/share/ngx_http_sqlite_module/examples/reference/sql/upsert-feature.sql";
sqlite_batch_transaction on;
sqlite_param id json:id required raw;
sqlite_param label json:label required raw;
}
# Off is appropriate here because both statements are independent reads.
location = /examples/batch/off {
sqlite;
sqlite_operation non_transactional_batch;
sqlite_method GET;
sqlite_batch "SELECT 1 AS ignored; SELECT 'transaction off' AS result;";
sqlite_batch_transaction off;
sqlite_read_only on;
sqlite_allow_anon on;
}
location = /examples/errors/timeout {
sqlite;
sqlite_operation deadline_error;
sqlite_method GET;
sqlite_sql "WITH RECURSIVE counter(x) AS (VALUES(0) UNION ALL SELECT x + 1 FROM counter WHERE x < 100000000) SELECT sum(x) AS total FROM counter";
sqlite_query_timeout 1ms;
sqlite_read_only on;
sqlite_allow_anon on;
}
location = /examples/errors/row-limit {
sqlite;
sqlite_operation row_limit_error;
sqlite_method GET;
sqlite_sql "SELECT 1 AS value UNION ALL SELECT 2 AS value";
sqlite_max_rows 1;
sqlite_response json_rows;
sqlite_read_only on;
sqlite_allow_anon on;
}
location = /examples/errors/response-limit {
sqlite;
sqlite_operation response_limit_error;
sqlite_method GET;
sqlite_sql "SELECT hex(zeroblob(128)) AS value";
sqlite_max_response_size 32;
sqlite_read_only on;
sqlite_allow_anon on;
}
location = /examples/errors/body-limit {
sqlite;
sqlite_max_body_size 32;
sqlite_operation body_limit_error;
sqlite_method POST;
sqlite_sql "SELECT :value AS value";
sqlite_param value json:value required raw;
sqlite_read_only on;
sqlite_allow_anon on;
}
location = /examples/errors/invalid-utf8 {
sqlite;
sqlite_operation invalid_utf8_error;
sqlite_method GET;
sqlite_sql "SELECT CAST(x'80' AS TEXT) AS value";
sqlite_read_only on;
sqlite_allow_anon on;
}
location = /examples/errors/conflict {
sqlite;
sqlite_operation unique_conflict_error;
sqlite_method POST;
sqlite_batch "
INSERT INTO lock_examples (id, value, updated_at) VALUES ('duplicate', 'first', 0);
INSERT INTO lock_examples (id, value, updated_at) VALUES ('duplicate', 'second', 0);
";
sqlite_batch_transaction on;
sqlite_allow_anon on;
}
location = /examples/errors/constraint {
sqlite;
sqlite_operation constraint_error;
sqlite_method POST;
sqlite_sql "INSERT INTO lock_examples (id, value, updated_at) VALUES ('not-null-demo', NULL, 0)";
sqlite_allow_anon on;
}
location = /examples/errors/foreign-key {
sqlite;
sqlite_operation foreign_key_error;
sqlite_method POST;
sqlite_sql "INSERT INTO feature_children (id, sample_id) VALUES ('missing-parent-demo', 'does-not-exist')";
sqlite_allow_anon on;
}
# Call hold-lock in the background, then busy while BEGIN IMMEDIATE owns the
# writer lock. The short busy timeout produces a deterministic HTTP 503.
location = /examples/errors/hold-lock {
sqlite;
sqlite_connections 1;
sqlite_operation hold_writer_lock;
sqlite_method GET;
sqlite_batch "
BEGIN IMMEDIATE;
WITH RECURSIVE counter(x) AS (VALUES(0) UNION ALL SELECT x + 1 FROM counter WHERE x < 100000000) SELECT sum(x) AS total FROM counter;
COMMIT;
";
sqlite_batch_transaction off;
sqlite_query_timeout 800ms;
sqlite_allow_anon on;
}
location = /examples/errors/busy {
sqlite;
sqlite_busy_timeout 1ms;
sqlite_operation busy_error;
sqlite_method POST;
sqlite_sql "INSERT INTO lock_examples (id, value, updated_at) VALUES ('busy-demo', 'write', strftime('%s','now')) ON CONFLICT(id) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at WHERE lock_examples.value IS NOT excluded.value";
sqlite_allow_anon on;
}
}