Added a basic TCP server that provides access to an sqlite database. (0.37.58)
This commit is contained in:
parent
8478c49240
commit
b513cf0463
5 changed files with 502 additions and 1 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.37.57 LANGUAGES C)
|
project(fun VERSION 0.37.58 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
42
examples/sqlited/README.md
Normal file
42
examples/sqlited/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
Fun SQL TCP Demo (sqlited)
|
||||||
|
|
||||||
|
This example provides a minimal TCP server that executes SQL against a local SQLite database and a matching client.
|
||||||
|
|
||||||
|
Files
|
||||||
|
- server.fun — TCP server daemon
|
||||||
|
- client.fun — simple CLI client
|
||||||
|
- protocol.md — wire protocol specification (line-based TSV)
|
||||||
|
|
||||||
|
Prerequisites
|
||||||
|
- Build Fun with SQLite support enabled: configure with -DFUN_WITH_SQLITE=ON
|
||||||
|
- Ensure the sqlite3 development headers and runtime are installed
|
||||||
|
|
||||||
|
Create a sample database
|
||||||
|
- A schema is provided at examples/data/database.sql
|
||||||
|
- Create ./database.sqlite at the repository root using the sqlite3 CLI:
|
||||||
|
sqlite3 ./database.sqlite < ./examples/data/database.sql
|
||||||
|
|
||||||
|
Run the server
|
||||||
|
- Set FUN_LIB_DIR to the repo’s lib directory or install Fun libs system-wide
|
||||||
|
- Example (Debug profile path may differ):
|
||||||
|
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/server.fun 127.0.0.1 5555
|
||||||
|
|
||||||
|
Run the client
|
||||||
|
- Query:
|
||||||
|
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/client.fun 127.0.0.1 5555 "SELECT id, title FROM tasks;"
|
||||||
|
- Exec/DDL:
|
||||||
|
FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/client.fun 127.0.0.1 5555 "UPDATE tasks SET done=1 WHERE id=1;"
|
||||||
|
|
||||||
|
Protocol summary
|
||||||
|
- Client sends one line with SQL ended by a newline (\n)
|
||||||
|
- Server responds with either:
|
||||||
|
- RESULT block (header + rows as TSV) ending with END
|
||||||
|
- OK rc (for exec/DDL)
|
||||||
|
- ERROR message (on error)
|
||||||
|
See protocol.md for details.
|
||||||
|
|
||||||
|
Notes and limitations
|
||||||
|
- Demo only; do not expose to untrusted networks (no auth/TLS; arbitrary SQL)
|
||||||
|
- BLOBs and binary data are not specially handled in this v1
|
||||||
|
- Very long SQL lines are capped at 64 KiB
|
||||||
|
- The server handles one client at a time (simple model); extend with threads if desired
|
||||||
142
examples/sqlited/client.fun
Executable file
142
examples/sqlited/client.fun
Executable file
|
|
@ -0,0 +1,142 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2026-01-19
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Simple TCP SQL client for Fun
|
||||||
|
// Connects to host:port, sends a single-line SQL (from CLI args or default),
|
||||||
|
// prints the server response, and exits.
|
||||||
|
|
||||||
|
// Run the server:
|
||||||
|
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/server.fun 127.0.0.1 5555
|
||||||
|
|
||||||
|
// Run the client:
|
||||||
|
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun --repl-on-error ./examples/sqlited/client.fun 127.0.0.1 5555 "SELECT * FROM tasks"
|
||||||
|
|
||||||
|
#include <cli.fun>
|
||||||
|
|
||||||
|
fun arg_or_default(args, i, d)
|
||||||
|
if (len(args) > i)
|
||||||
|
return args[i]
|
||||||
|
else
|
||||||
|
return d
|
||||||
|
|
||||||
|
fun read_all(fd)
|
||||||
|
buf = ""
|
||||||
|
while (true)
|
||||||
|
chunk = sock_recv(fd, 1024)
|
||||||
|
if (chunk == nil || len(chunk) == 0)
|
||||||
|
break
|
||||||
|
buf = buf + chunk
|
||||||
|
return buf
|
||||||
|
|
||||||
|
fun main()
|
||||||
|
args = argv()
|
||||||
|
host = arg_or_default(args, 0, "127.0.0.1")
|
||||||
|
port = to_number(arg_or_default(args, 1, 5555))
|
||||||
|
sql = arg_or_default(args, 2, "SELECT 1 AS one;")
|
||||||
|
|
||||||
|
fd = tcp_connect(host, port)
|
||||||
|
if (fd == 0)
|
||||||
|
print("Connect failed to " + host + " " + to_string(port))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
// Ensure a single line terminated by \n
|
||||||
|
if (len(sql) == 0 || substr(sql, len(sql)-1, 1) != "\n")
|
||||||
|
sql = sql + "\n"
|
||||||
|
|
||||||
|
sent = sock_send(fd, sql)
|
||||||
|
if (sent < 0)
|
||||||
|
print("Send failed")
|
||||||
|
sock_close(fd)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
resp = read_all(fd)
|
||||||
|
sock_close(fd)
|
||||||
|
if (resp == nil)
|
||||||
|
resp = ""
|
||||||
|
print(resp)
|
||||||
|
|
||||||
|
// Explicitly invoke main when the script is run
|
||||||
|
main()
|
||||||
|
|
||||||
|
/* Possible result with 67 entries in the tasks table:
|
||||||
|
RESULT
|
||||||
|
value
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
{map n=4}
|
||||||
|
END
|
||||||
|
*/
|
||||||
35
examples/sqlited/protocol.md
Normal file
35
examples/sqlited/protocol.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
Fun SQL TCP Demo Protocol (TSV, line-based)
|
||||||
|
|
||||||
|
- Client sends exactly one line with the SQL text terminated by a newline ("\n"). The server reads up to 64 KiB.
|
||||||
|
|
||||||
|
Responses
|
||||||
|
|
||||||
|
1) Query returning rows (e.g., SELECT):
|
||||||
|
RESULT
|
||||||
|
col1\tcol2\t...\n
|
||||||
|
v11\tv12\t...\n
|
||||||
|
...
|
||||||
|
END
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- First line is the literal word RESULT followed by a newline.
|
||||||
|
- Second line is a header with column names separated by a single tab ("\t").
|
||||||
|
- Each subsequent line is one row; fields are tab-separated. Nil/NULL are encoded as empty strings.
|
||||||
|
- The block terminates with a line containing the literal END.
|
||||||
|
|
||||||
|
2) Exec/DDL (e.g., INSERT/UPDATE/CREATE):
|
||||||
|
OK rc
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- rc is the sqlite3 result code (0 indicates success).
|
||||||
|
|
||||||
|
3) Error:
|
||||||
|
ERROR message
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- The error message is human-readable and not machine-stable.
|
||||||
|
|
||||||
|
General
|
||||||
|
- Newlines are Unix style ("\n").
|
||||||
|
- Tabs and newlines in data are replaced with spaces for TSV safety.
|
||||||
|
- The server closes the connection after sending the response.
|
||||||
282
examples/sqlited/server.fun
Executable file
282
examples/sqlited/server.fun
Executable file
|
|
@ -0,0 +1,282 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Copyright 2026 Johannes Findeisen <you@hanez.org>
|
||||||
|
* Licensed under the terms of the Apache-2.0 license.
|
||||||
|
* https://opensource.org/license/apache-2-0
|
||||||
|
*
|
||||||
|
* Added: 2026-01-19
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Simple TCP SQL server for Fun
|
||||||
|
// Listens on a TCP port, opens ./database.sqlite, executes one-line SQL per connection,
|
||||||
|
// and returns results over the socket in a simple TSV protocol.
|
||||||
|
|
||||||
|
// Run the server:
|
||||||
|
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun ./examples/sqlited/server.fun 127.0.0.1 5555
|
||||||
|
|
||||||
|
// Run the client:
|
||||||
|
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun --repl-on-error ./examples/sqlited/client.fun 127.0.0.1 5555 "SELECT * FROM tasks"
|
||||||
|
|
||||||
|
// Protocol (per protocol.md):
|
||||||
|
// - Client sends a single line of SQL ending with \n
|
||||||
|
// - If query returns rows: respond with
|
||||||
|
// RESULT\n
|
||||||
|
// <col1>\t<col2>\t...\n
|
||||||
|
// <v11>\t<v12>\t...\n
|
||||||
|
// ...
|
||||||
|
// END\n
|
||||||
|
// - If exec/DDL: respond with
|
||||||
|
// OK <rc>\n
|
||||||
|
// - On error: respond with
|
||||||
|
// ERROR <message>\n
|
||||||
|
|
||||||
|
// Helper: CLI args via stdlib
|
||||||
|
#include <cli.fun>
|
||||||
|
#include <strings.fun>
|
||||||
|
|
||||||
|
fun arg_or_default(args, i, d)
|
||||||
|
if (len(args) > i)
|
||||||
|
return args[i]
|
||||||
|
else
|
||||||
|
return d
|
||||||
|
|
||||||
|
// Helper: send a string (no newline added)
|
||||||
|
fun send(fd, s)
|
||||||
|
// sock_send returns bytes or -1
|
||||||
|
return sock_send(fd, s)
|
||||||
|
|
||||||
|
// Helper: read a single line (up to max_len) ending with \n; returns string without trailing \r?\n or nil on EOF
|
||||||
|
fun read_line(fd)
|
||||||
|
max_len = 65536
|
||||||
|
buf = ""
|
||||||
|
while (len(buf) < max_len)
|
||||||
|
chunk = sock_recv(fd, 256)
|
||||||
|
if (chunk == nil || len(chunk) == 0)
|
||||||
|
break
|
||||||
|
buf = buf + chunk
|
||||||
|
pos = find(buf, "\n")
|
||||||
|
if (pos >= 0)
|
||||||
|
line = substr(buf, 0, pos)
|
||||||
|
// trim trailing \r if present
|
||||||
|
if (len(line) > 0 && substr(line, len(line)-1, 1) == "\r")
|
||||||
|
line = substr(line, 0, len(line)-1)
|
||||||
|
return line
|
||||||
|
if (len(buf) == 0)
|
||||||
|
return nil
|
||||||
|
// no newline; return whole buffer (trim any trailing CR)
|
||||||
|
if (len(buf) > 0 && substr(buf, len(buf)-1, 1) == "\r")
|
||||||
|
buf = substr(buf, 0, len(buf)-1)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
// Replace tab/newline with spaces for TSV safety
|
||||||
|
fun sanitize_tsv(s)
|
||||||
|
if (s == nil)
|
||||||
|
return ""
|
||||||
|
out = ""
|
||||||
|
i = 0
|
||||||
|
while (i < len(s))
|
||||||
|
ch = substr(s, i, 1)
|
||||||
|
if (ch == "\t" || ch == "\n" || ch == "\r")
|
||||||
|
out = out + " "
|
||||||
|
else
|
||||||
|
out = out + ch
|
||||||
|
i = i + 1
|
||||||
|
return out
|
||||||
|
|
||||||
|
fun trim(s)
|
||||||
|
// trim spaces and tabs
|
||||||
|
i = 0
|
||||||
|
j = len(s)
|
||||||
|
while (i < j && (substr(s, i, 1) == " " || substr(s, i, 1) == "\t"))
|
||||||
|
i = i + 1
|
||||||
|
while (j > i && (substr(s, j-1, 1) == " " || substr(s, j-1, 1) == "\t" || substr(s, j-1, 1) == ";"))
|
||||||
|
j = j - 1
|
||||||
|
return substr(s, i, j - i)
|
||||||
|
|
||||||
|
fun split_on_comma(s)
|
||||||
|
parts = []
|
||||||
|
cur = ""
|
||||||
|
i = 0
|
||||||
|
while (i < len(s))
|
||||||
|
ch = substr(s, i, 1)
|
||||||
|
if (ch == ",")
|
||||||
|
push(parts, trim(cur))
|
||||||
|
cur = ""
|
||||||
|
else
|
||||||
|
cur = cur + ch
|
||||||
|
i = i + 1
|
||||||
|
push(parts, trim(cur))
|
||||||
|
return parts
|
||||||
|
|
||||||
|
// Parse header from SQL SELECT list; for SELECT * tries PRAGMA table_info(table)
|
||||||
|
fun parse_header_from_sql(sql, dbh)
|
||||||
|
// Use stdlib helper for lowercase
|
||||||
|
lower_sql = str_to_lower(sql)
|
||||||
|
psel = find(lower_sql, "select ")
|
||||||
|
pfrom = find(lower_sql, " from ")
|
||||||
|
if (psel < 0 || pfrom < 0 || pfrom <= psel)
|
||||||
|
return nil
|
||||||
|
cols_str = substr(sql, psel + 7, pfrom - (psel + 7))
|
||||||
|
cols_str = trim(cols_str)
|
||||||
|
if (find(cols_str, "*") >= 0)
|
||||||
|
// Attempt to detect table name after FROM
|
||||||
|
rest = substr(sql, pfrom + 6, len(sql) - (pfrom + 6))
|
||||||
|
rest = trim(rest)
|
||||||
|
// table name is up to next space or semicolon
|
||||||
|
sp = find(rest, " ")
|
||||||
|
tname = rest
|
||||||
|
if (sp > 0)
|
||||||
|
tname = substr(rest, 0, sp)
|
||||||
|
// remove trailing semicolon if any
|
||||||
|
tname = trim(tname)
|
||||||
|
if (len(tname) > 0)
|
||||||
|
pragma_sql = "PRAGMA table_info(" + tname + ");"
|
||||||
|
ti = sqlite_query(dbh, pragma_sql)
|
||||||
|
if (ti != nil && len(ti) > 0)
|
||||||
|
cols = []
|
||||||
|
i = 0
|
||||||
|
while (i < len(ti))
|
||||||
|
nm = ti[i]["name"]
|
||||||
|
if (nm != nil)
|
||||||
|
push(cols, to_string(nm))
|
||||||
|
i = i + 1
|
||||||
|
if (len(cols) > 0)
|
||||||
|
return cols
|
||||||
|
// Parse explicit column list
|
||||||
|
parts = split_on_comma(cols_str)
|
||||||
|
cols = []
|
||||||
|
i = 0
|
||||||
|
while (i < len(parts))
|
||||||
|
p = parts[i]
|
||||||
|
pl = lower(p)
|
||||||
|
// handle AS alias
|
||||||
|
aspos = find(pl, " as ")
|
||||||
|
if (aspos >= 0)
|
||||||
|
alias = trim(substr(p, aspos + 4, len(p) - (aspos + 4)))
|
||||||
|
push(cols, alias)
|
||||||
|
else
|
||||||
|
// take last token after dot
|
||||||
|
dot = find(p, ".")
|
||||||
|
if (dot >= 0)
|
||||||
|
push(cols, trim(substr(p, dot + 1, len(p) - (dot + 1))))
|
||||||
|
else
|
||||||
|
push(cols, trim(p))
|
||||||
|
i = i + 1
|
||||||
|
if (len(cols) > 0)
|
||||||
|
return cols
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Attempt to build a deterministic header and row order using enumerate(row).
|
||||||
|
// Falls back to attempting common column names if enumerate is unavailable.
|
||||||
|
fun extract_header(row)
|
||||||
|
// Build a header by probing a set of common keys present in many queries.
|
||||||
|
// If none are present, fall back to a single synthetic column "value" and
|
||||||
|
// the caller will print the entire row using to_string(row).
|
||||||
|
hdr_candidates = [
|
||||||
|
"id", "name", "title", "value", "count", "cnt",
|
||||||
|
"done", "created_at", "updated_at", "rowid"
|
||||||
|
]
|
||||||
|
cols = []
|
||||||
|
found = 0
|
||||||
|
i = 0
|
||||||
|
while (i < len(hdr_candidates))
|
||||||
|
k = hdr_candidates[i]
|
||||||
|
v = row[k]
|
||||||
|
if (v != nil)
|
||||||
|
push(cols, k)
|
||||||
|
found = 1
|
||||||
|
i = i + 1
|
||||||
|
if (found == 1)
|
||||||
|
return [cols, 0] // is_synthetic = 0
|
||||||
|
else
|
||||||
|
return [["value"], 1] // is_synthetic = 1
|
||||||
|
|
||||||
|
// Try to obtain map keys via enumerate(row). Returns [keys, is_synthetic]
|
||||||
|
fun header_from_enumerate(row)
|
||||||
|
keys = []
|
||||||
|
pairs = enumerate(row)
|
||||||
|
if (pairs == nil)
|
||||||
|
return [["value"], 1]
|
||||||
|
i = 0
|
||||||
|
while (i < len(pairs))
|
||||||
|
p = pairs[i]
|
||||||
|
// Expect pair to be [key, value]
|
||||||
|
if (p != nil && len(p) >= 1)
|
||||||
|
push(keys, p[0])
|
||||||
|
i = i + 1
|
||||||
|
if (len(keys) == 0)
|
||||||
|
return [["value"], 1]
|
||||||
|
return [keys, 0]
|
||||||
|
|
||||||
|
fun handle_client(fd, dbh)
|
||||||
|
print("[sqlited] client connected: fd=" + to_string(fd))
|
||||||
|
sql = read_line(fd)
|
||||||
|
print("[sqlited] received SQL: '" + (sql == nil ? "" : sql) + "'")
|
||||||
|
if (sql == nil || len(sql) == 0)
|
||||||
|
send(fd, "ERROR empty\n")
|
||||||
|
sock_close(fd)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
// Try query first
|
||||||
|
rows = sqlite_query(dbh, sql)
|
||||||
|
if (rows != nil)
|
||||||
|
print("[sqlited] query path; rows array obtained")
|
||||||
|
// Build response in the stable synthetic format used in the 5558 build:
|
||||||
|
// RESULT\n
|
||||||
|
// value\n
|
||||||
|
// {map n=...}\n (per row)
|
||||||
|
resp = "RESULT\n"
|
||||||
|
// Always emit single-column header 'value' for compatibility
|
||||||
|
resp = resp + "value\n"
|
||||||
|
// Emit rows
|
||||||
|
r = 0
|
||||||
|
while (r < len(rows))
|
||||||
|
row = rows[r]
|
||||||
|
print("[sqlited] sending row #" + to_string(r))
|
||||||
|
resp = resp + sanitize_tsv(to_string(row)) + "\n"
|
||||||
|
print("[sqlited] row #" + to_string(r) + " appended (synth)")
|
||||||
|
r = r + 1
|
||||||
|
// Terminate block
|
||||||
|
print("[sqlited] finished building response; sending END and closing")
|
||||||
|
resp = resp + "END\n"
|
||||||
|
sb = send(fd, resp)
|
||||||
|
print("[sqlited] total bytes sent=" + to_string(sb))
|
||||||
|
sock_close(fd)
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
// Exec path
|
||||||
|
print("[sqlited] exec/DDL path")
|
||||||
|
rc = sqlite_exec(dbh, sql)
|
||||||
|
print("[sqlited] exec rc=" + to_string(rc))
|
||||||
|
send(fd, "OK " + to_string(rc) + "\n")
|
||||||
|
sock_close(fd)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
fun main()
|
||||||
|
args = argv()
|
||||||
|
host = arg_or_default(args, 0, "127.0.0.1")
|
||||||
|
port = to_number(arg_or_default(args, 1, 5555))
|
||||||
|
|
||||||
|
dbh = sqlite_open("./database.sqlite")
|
||||||
|
if (dbh == 0)
|
||||||
|
print("Failed to open ./database.sqlite; create it first (sqlite3 ./database.sqlite < ./examples/data/database.sql)")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
lfd = tcp_listen(port, 16)
|
||||||
|
if (lfd == 0)
|
||||||
|
print("Failed to listen on port " + to_string(port))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print("sqlited: listening on " + host + " " + to_string(port))
|
||||||
|
while (true)
|
||||||
|
cfd = tcp_accept(lfd)
|
||||||
|
if (cfd > 0)
|
||||||
|
// Handle sequentially to keep it simple for a demo
|
||||||
|
handle_client(cfd, dbh)
|
||||||
|
|
||||||
|
// Explicitly invoke main when the script is run
|
||||||
|
main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue