IMPROVEMENT: bring Python trigger support to parity with PHP.
Add SQL bridge, support functions, cron jobs, error handling, and PyPI version pinning so Python triggers match PHP capabilities. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
de40a2253d
commit
14f96e78f9
@ -9,6 +9,7 @@ v0.4 - has an "export" and "import" module that converts sql data into a "git re
|
|||||||
|
|
||||||
## AS IS:
|
## AS IS:
|
||||||
|
|
||||||
|
v0.4 - Python triggers at parity with PHP (support functions, sql bridge, cron, error handling)
|
||||||
v0.3 - supports Javascript / ECMA Script, and npm package manager
|
v0.3 - supports Javascript / ECMA Script, and npm package manager
|
||||||
v0.2 - has a function called "sql_read_hidrate()" that gives the foreign keys hidrated
|
v0.2 - has a function called "sql_read_hidrate()" that gives the foreign keys hidrated
|
||||||
- (useful for trigger functions like telegram outbox that need data like recipients hidrated)
|
- (useful for trigger functions like telegram outbox that need data like recipients hidrated)
|
||||||
|
|||||||
14
install/SYS_PRD_BND.CronJobs.sql
Normal file
14
install/SYS_PRD_BND.CronJobs.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
USE SYS_PRD_BND;
|
||||||
|
CREATE TABLE IF NOT EXISTS `CronJobs` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`Minute` int(11) NOT NULL DEFAULT -1,
|
||||||
|
`Hour` int(11) NOT NULL DEFAULT -1,
|
||||||
|
`DayOfMonth` int(11) NOT NULL DEFAULT -1,
|
||||||
|
`Month` int(11) NOT NULL DEFAULT -1,
|
||||||
|
`DayOfWeek` int(11) NOT NULL DEFAULT -1,
|
||||||
|
`PhpCode` text DEFAULT NULL,
|
||||||
|
`PyCode` text DEFAULT NULL,
|
||||||
|
`LastUpdated` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
`LastError` text DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
);
|
||||||
@ -2,6 +2,7 @@ USE SYS_PRD_BND;
|
|||||||
CREATE TABLE `PyPi` (
|
CREATE TABLE `PyPi` (
|
||||||
`LibName` varchar(255) NOT NULL,
|
`LibName` varchar(255) NOT NULL,
|
||||||
`AliasName` varchar(255) DEFAULT NULL,
|
`AliasName` varchar(255) DEFAULT NULL,
|
||||||
|
`VersionOrTag` varchar(25) DEFAULT NULL,
|
||||||
`LastUpdated` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
`LastUpdated` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
PRIMARY KEY (`LibName`)
|
PRIMARY KEY (`LibName`)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -20,6 +20,8 @@ function generatePHPCronCode($functionName, $PhpCode) {
|
|||||||
// Assemble the complete PHP script
|
// Assemble the complete PHP script
|
||||||
$wrappedPhpCode = <<<PHP
|
$wrappedPhpCode = <<<PHP
|
||||||
<?php
|
<?php
|
||||||
|
require_once '.env.php';
|
||||||
|
|
||||||
// Define constants
|
// Define constants
|
||||||
$constants
|
$constants
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,8 @@ function generatePHPTriggerCode($functionName, $onUpdate_phpCode, $row) {
|
|||||||
// Assemble the complete PHP script
|
// Assemble the complete PHP script
|
||||||
$phpCode = <<<PHP
|
$phpCode = <<<PHP
|
||||||
<?php
|
<?php
|
||||||
|
require_once '.env.php';
|
||||||
|
|
||||||
// Define constants
|
// Define constants
|
||||||
$constants
|
$constants
|
||||||
|
|
||||||
|
|||||||
32
plt/generatePythonCronCode.inc.php
Normal file
32
plt/generatePythonCronCode.inc.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Generates Python code for sandbox cron execution.
|
||||||
|
*
|
||||||
|
* @param string $functionName The name of the Python function to generate.
|
||||||
|
* @param string $pyCode The Python code to embed within the generated function.
|
||||||
|
*
|
||||||
|
* @return string The complete Python code ready for sandbox execution.
|
||||||
|
*/
|
||||||
|
function generatePythonCronCode($functionName, $pyCode) {
|
||||||
|
$constants = getPythonConstantsDefinition();
|
||||||
|
$supportFunctions = getPythonSupportFunctionsDefinition();
|
||||||
|
$imports = "import json\nimport sys as _sys\nimport os\n\n_sys.path.insert(0, os.path.join(os.getcwd(), 'sys'))\nfrom db_runtime import sql, sql_read, sql_write, sql_read_and_hydrate, get_tbl_primary_key_col_name, run_process\n" . getPythonImports();
|
||||||
|
|
||||||
|
$indentedCode = implode("\n", array_map(fn($line) => ' ' . $line, explode("\n", $pyCode)));
|
||||||
|
|
||||||
|
$pythonCode = <<<PY
|
||||||
|
$imports
|
||||||
|
$constants
|
||||||
|
$supportFunctions
|
||||||
|
def $functionName(error):
|
||||||
|
$indentedCode
|
||||||
|
|
||||||
|
error = [None]
|
||||||
|
_ret = $functionName(error)
|
||||||
|
if _ret is False or error[0]:
|
||||||
|
_sys.stderr.write("Error: " + str(error[0] or "") + "\\n")
|
||||||
|
_sys.exit(1)
|
||||||
|
PY;
|
||||||
|
|
||||||
|
return $pythonCode;
|
||||||
|
}
|
||||||
@ -9,42 +9,27 @@
|
|||||||
* @return string The complete Python code ready for sandbox execution.
|
* @return string The complete Python code ready for sandbox execution.
|
||||||
*/
|
*/
|
||||||
function generatePythonTriggerCode($functionName, $pyCode, $row) {
|
function generatePythonTriggerCode($functionName, $pyCode, $row) {
|
||||||
// Build constant definitions
|
$constants = getPythonConstantsDefinition();
|
||||||
$constants = '';
|
$supportFunctions = getPythonSupportFunctionsDefinition();
|
||||||
foreach (sql("SELECT Name, Type, Value FROM SYS_PRD_BND.Constants") as $const) {
|
$imports = "import json\nimport sys as _sys\nimport os\n\n_sys.path.insert(0, os.path.join(os.getcwd(), 'sys'))\nfrom db_runtime import sql, sql_read, sql_write, sql_read_and_hydrate, get_tbl_primary_key_col_name, run_process\n" . getPythonImports();
|
||||||
switch ($const['Type']) {
|
|
||||||
case 'String':
|
|
||||||
$val = "'" . str_replace("'", "\\'", $const['Value']) . "'";
|
|
||||||
break;
|
|
||||||
case 'Json':
|
|
||||||
$val = 'json.loads(' . json_encode($const['Value']) . ')';
|
|
||||||
break;
|
|
||||||
default: // Int or Double
|
|
||||||
$val = $const['Value'];
|
|
||||||
}
|
|
||||||
$constants .= "{$const['Name']} = {$val}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare imports
|
$rowJson = json_encode($row, JSON_UNESCAPED_UNICODE);
|
||||||
$imports = "import json\n" . getPythonImports();
|
|
||||||
|
|
||||||
// Prepare row data for Python
|
|
||||||
$rowJson = json_encode($row, JSON_UNESCAPED_UNICODE);
|
|
||||||
$rowJsonPyLiteral = json_encode($rowJson);
|
$rowJsonPyLiteral = json_encode($rowJson);
|
||||||
|
|
||||||
// Indent user-provided Python code
|
|
||||||
$indentedCode = implode("\n", array_map(fn($line) => ' ' . $line, explode("\n", $pyCode)));
|
$indentedCode = implode("\n", array_map(fn($line) => ' ' . $line, explode("\n", $pyCode)));
|
||||||
|
|
||||||
// Assemble complete Python script
|
|
||||||
$pythonCode = <<<PY
|
$pythonCode = <<<PY
|
||||||
$imports
|
$imports
|
||||||
$constants
|
$constants
|
||||||
|
$supportFunctions
|
||||||
def $functionName(data, error):
|
def $functionName(data, error):
|
||||||
$indentedCode
|
$indentedCode
|
||||||
|
|
||||||
data = json.loads($rowJsonPyLiteral)
|
data = json.loads($rowJsonPyLiteral)
|
||||||
error = None
|
error = [None]
|
||||||
$functionName(data, error)
|
_ret = $functionName(data, error)
|
||||||
|
if _ret is False or error[0]:
|
||||||
|
_sys.stderr.write("Error: " + str(error[0] or "") + "\\n")
|
||||||
|
_sys.exit(1)
|
||||||
print(json.dumps(data))
|
print(json.dumps(data))
|
||||||
PY;
|
PY;
|
||||||
|
|
||||||
|
|||||||
23
plt/getPythonConstantsDefinition.inc.php
Normal file
23
plt/getPythonConstantsDefinition.inc.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Builds Python constant assignments from SYS_PRD_BND.Constants.
|
||||||
|
*/
|
||||||
|
function getPythonConstantsDefinition() {
|
||||||
|
$constants = "";
|
||||||
|
|
||||||
|
foreach (sql("SELECT Name, Type, Value FROM SYS_PRD_BND.Constants") as $const) {
|
||||||
|
switch ($const['Type']) {
|
||||||
|
case 'String':
|
||||||
|
$val = "'" . str_replace("'", "\\'", $const['Value']) . "'";
|
||||||
|
break;
|
||||||
|
case 'Json':
|
||||||
|
$val = 'json.loads(' . json_encode($const['Value']) . ')';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$val = $const['Value'];
|
||||||
|
}
|
||||||
|
$constants .= "{$const['Name']} = {$val}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $constants;
|
||||||
|
}
|
||||||
17
plt/getPythonSupportFunctionsDefinition.inc.php
Normal file
17
plt/getPythonSupportFunctionsDefinition.inc.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Builds Python support function definitions from SYS_PRD_BND.SupportFunctions.
|
||||||
|
*/
|
||||||
|
function getPythonSupportFunctionsDefinition() {
|
||||||
|
$functions = "";
|
||||||
|
|
||||||
|
foreach (sql("SELECT Name, InputArgs_json, PythonCode FROM SYS_PRD_BND.SupportFunctions WHERE PythonCode IS NOT NULL") as $f) {
|
||||||
|
$inputArgs = json_decode($f['InputArgs_json'], true);
|
||||||
|
$argNames = is_array($inputArgs) ? array_keys($inputArgs) : [];
|
||||||
|
$argsString = implode(', ', $argNames);
|
||||||
|
$indentedBody = implode("\n", array_map(fn($line) => ' ' . $line, explode("\n", $f['PythonCode'])));
|
||||||
|
$functions .= "def {$f['Name']}($argsString):\n{$indentedBody}\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $functions;
|
||||||
|
}
|
||||||
@ -27,12 +27,12 @@
|
|||||||
* Columns: Name, Type, Value
|
* Columns: Name, Type, Value
|
||||||
*
|
*
|
||||||
* - SYS_PRD_BND.SupportFunctions:
|
* - SYS_PRD_BND.SupportFunctions:
|
||||||
* Stores reusable PHP functions available during trigger execution.
|
* Stores reusable functions available during trigger execution.
|
||||||
* Columns: Name, InputArgs_json, PhpCode
|
* Columns: Name, InputArgs_json, PhpCode, PythonCode, JavascriptCode
|
||||||
*
|
*
|
||||||
* - SYS_PRD_BND.PyPi:
|
* - SYS_PRD_BND.PyPi:
|
||||||
* Lists external Python libraries imported into Python trigger execution.
|
* Lists external Python libraries imported into Python trigger execution.
|
||||||
* Columns: LibName, AliasName
|
* Columns: LibName, AliasName, VersionOrTag
|
||||||
*
|
*
|
||||||
* - SYS_PRD_BND.Npm:
|
* - SYS_PRD_BND.Npm:
|
||||||
* Lists external Node modules imported into JavaScript trigger execution.
|
* Lists external Node modules imported into JavaScript trigger execution.
|
||||||
@ -123,7 +123,7 @@ function processCronJobs() {
|
|||||||
|
|
||||||
// 3. FETCH JOBS
|
// 3. FETCH JOBS
|
||||||
// We check for an exact match OR a wildcard (-1)
|
// We check for an exact match OR a wildcard (-1)
|
||||||
$sql = "SELECT id, PhpCode
|
$sql = "SELECT id, PhpCode, PyCode
|
||||||
FROM SYS_PRD_BND.CronJobs
|
FROM SYS_PRD_BND.CronJobs
|
||||||
WHERE
|
WHERE
|
||||||
(Minute = MINUTE(NOW()) OR Minute = -1)
|
(Minute = MINUTE(NOW()) OR Minute = -1)
|
||||||
@ -138,9 +138,11 @@ function processCronJobs() {
|
|||||||
if ($jobs) {
|
if ($jobs) {
|
||||||
foreach ($jobs as $cronJob) {
|
foreach ($jobs as $cronJob) {
|
||||||
if (!empty($cronJob["PhpCode"])) {
|
if (!empty($cronJob["PhpCode"])) {
|
||||||
// Pass a unique identifier (cronJob_ID) to the sandbox runner
|
|
||||||
runPHPCronCode("cronJob_" . $cronJob["id"], $cronJob["PhpCode"], $cronJob["id"]);
|
runPHPCronCode("cronJob_" . $cronJob["id"], $cronJob["PhpCode"], $cronJob["id"]);
|
||||||
}
|
}
|
||||||
|
if (!empty($cronJob["PyCode"])) {
|
||||||
|
runPythonCronCode("cronJob_" . $cronJob["id"], $cronJob["PyCode"], $cronJob["id"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,6 +157,17 @@ function runPHPCronCode($functionName,$code,$cronJobId) {
|
|||||||
handleCronJobExecutionResult($result, $stdout,$stderr,$cronJobId);
|
handleCronJobExecutionResult($result, $stdout,$stderr,$cronJobId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes database-level dynamic Python cron code.
|
||||||
|
*/
|
||||||
|
function runPythonCronCode($functionName, $code, $cronJobId) {
|
||||||
|
echo "Running Python CRON code for cronJobId $cronJobId...\n";
|
||||||
|
$pyCode = generatePythonCronCode($functionName, $code);
|
||||||
|
$result = runSandboxedPython($pyCode, $stdout, $stderr);
|
||||||
|
|
||||||
|
handleCronJobExecutionResult($result, $stdout, $stderr, $cronJobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures the 'LastUpdated' column exists, adding it if missing.
|
* Ensures the 'LastUpdated' column exists, adding it if missing.
|
||||||
|
|||||||
@ -11,13 +11,19 @@
|
|||||||
function runSandboxedPython($code, &$stdout = null, &$stderr = null) {
|
function runSandboxedPython($code, &$stdout = null, &$stderr = null) {
|
||||||
// Create a temporary file to hold the Python code
|
// Create a temporary file to hold the Python code
|
||||||
$tempFile = tempnam(sys_get_temp_dir(), 'sandboxed_') . '.py';
|
$tempFile = tempnam(sys_get_temp_dir(), 'sandboxed_') . '.py';
|
||||||
echo "Creating ".redText("python temp code file")." for sandboxed execution at: $tempFile \n";
|
|
||||||
|
|
||||||
// Write the generated Python code to the temporary file
|
// Write the generated Python code to the temporary file
|
||||||
file_put_contents($tempFile, $code);
|
file_put_contents($tempFile, $code);
|
||||||
|
|
||||||
// Prepare the command for executing the Python code
|
// Prepare the command for executing the Python code
|
||||||
$command = "/usr/bin/python3 " . escapeshellarg($tempFile);
|
$pythonBin = getenv('REACTIVE_PYTHON_BIN') ?: '/usr/bin/python3';
|
||||||
|
if (!is_file($pythonBin)) {
|
||||||
|
$which = trim((string) shell_exec('command -v python3 2>/dev/null'));
|
||||||
|
if ($which !== '') {
|
||||||
|
$pythonBin = $which;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$command = $pythonBin . " " . escapeshellarg($tempFile);
|
||||||
|
|
||||||
// Descriptor spec to capture stdout and stderr
|
// Descriptor spec to capture stdout and stderr
|
||||||
$descriptorspec = [
|
$descriptorspec = [
|
||||||
|
|||||||
@ -12,13 +12,15 @@ try {
|
|||||||
|
|
||||||
// Fetch dependencies from PyPi table
|
// Fetch dependencies from PyPi table
|
||||||
// Note: We select LibName. If AliasName represents a specific version or URI, it can be appended.
|
// Note: We select LibName. If AliasName represents a specific version or URI, it can be appended.
|
||||||
$stmt = $pdo->query("SELECT LibName, AliasName FROM PyPi ORDER BY LibName");
|
$stmt = $pdo->query("SELECT LibName, VersionOrTag FROM PyPi ORDER BY LibName");
|
||||||
|
|
||||||
$requirements = [];
|
$requirements = [];
|
||||||
foreach ($stmt as $row) {
|
foreach ($stmt as $row) {
|
||||||
// Standard requirements.txt format is usually "PackageName" or "PackageName==Version"
|
$line = $row['LibName'];
|
||||||
// If AliasName is used for versioning, you could use: $row['LibName'] . "==" . $row['AliasName']
|
if (!empty($row['VersionOrTag'])) {
|
||||||
$requirements[] = $row['LibName'];
|
$line .= '==' . $row['VersionOrTag'];
|
||||||
|
}
|
||||||
|
$requirements[] = $line;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build requirements.txt content (Plain Text, one per line)
|
// Build requirements.txt content (Plain Text, one per line)
|
||||||
|
|||||||
120
sys/db_runtime.py
Normal file
120
sys/db_runtime.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
"""Database runtime for sandboxed Python triggers (mirrors sys/sys.php via sql_bridge.php)."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def _project_root() -> str:
|
||||||
|
return os.getcwd()
|
||||||
|
|
||||||
|
|
||||||
|
def _php_binary() -> str:
|
||||||
|
for candidate in (
|
||||||
|
os.environ.get("REACTIVE_PHP_BIN"),
|
||||||
|
"/usr/bin/php",
|
||||||
|
shutil.which("php"),
|
||||||
|
):
|
||||||
|
if candidate and os.path.isfile(candidate):
|
||||||
|
return candidate
|
||||||
|
if candidate and shutil.which(candidate):
|
||||||
|
return shutil.which(candidate) # type: ignore[return-value]
|
||||||
|
raise RuntimeError("PHP binary not found for sql_bridge")
|
||||||
|
|
||||||
|
|
||||||
|
def _bridge(action: str, payload: dict) -> Any:
|
||||||
|
bridge = os.path.join(_project_root(), "sys", "sql_bridge.php")
|
||||||
|
proc = subprocess.run(
|
||||||
|
[_php_binary(), bridge],
|
||||||
|
input=json.dumps({"action": action, **payload}),
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
err = proc.stderr.strip()
|
||||||
|
try:
|
||||||
|
err_obj = json.loads(err)
|
||||||
|
err = err_obj.get("error", err)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
raise RuntimeError(err or "sql_bridge failed")
|
||||||
|
data = json.loads(proc.stdout)
|
||||||
|
return data["result"]
|
||||||
|
|
||||||
|
|
||||||
|
def sql(
|
||||||
|
query: str,
|
||||||
|
db_host: Optional[str] = None,
|
||||||
|
db_user: Optional[str] = None,
|
||||||
|
db_pass: Optional[str] = None,
|
||||||
|
db_name: Optional[str] = None,
|
||||||
|
) -> Any:
|
||||||
|
return _bridge(
|
||||||
|
"sql",
|
||||||
|
{
|
||||||
|
"query": query,
|
||||||
|
"db_host": db_host,
|
||||||
|
"db_user": db_user,
|
||||||
|
"db_pass": db_pass,
|
||||||
|
"db_name": db_name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sql_read(
|
||||||
|
query: str,
|
||||||
|
db_host: Optional[str] = None,
|
||||||
|
db_user: Optional[str] = None,
|
||||||
|
db_pass: Optional[str] = None,
|
||||||
|
db_name: Optional[str] = None,
|
||||||
|
) -> Any:
|
||||||
|
return _bridge(
|
||||||
|
"sql_read",
|
||||||
|
{
|
||||||
|
"query": query,
|
||||||
|
"db_host": db_host,
|
||||||
|
"db_user": db_user,
|
||||||
|
"db_pass": db_pass,
|
||||||
|
"db_name": db_name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sql_write(
|
||||||
|
query: str,
|
||||||
|
db_host: Optional[str] = None,
|
||||||
|
db_user: Optional[str] = None,
|
||||||
|
db_pass: Optional[str] = None,
|
||||||
|
db_name: Optional[str] = None,
|
||||||
|
) -> Any:
|
||||||
|
return _bridge(
|
||||||
|
"sql_write",
|
||||||
|
{
|
||||||
|
"query": query,
|
||||||
|
"db_host": db_host,
|
||||||
|
"db_user": db_user,
|
||||||
|
"db_pass": db_pass,
|
||||||
|
"db_name": db_name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sql_read_and_hydrate(query: str) -> Any:
|
||||||
|
return _bridge("sql_read_and_hydrate", {"query": query})
|
||||||
|
|
||||||
|
|
||||||
|
def get_tbl_primary_key_col_name(table: str) -> list:
|
||||||
|
return _bridge("get_tbl_primary_key_col_name", {"table": table})
|
||||||
|
|
||||||
|
|
||||||
|
def run_process(cmd: str, stdin: str = "") -> tuple[int, str, str]:
|
||||||
|
proc = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
input=stdin,
|
||||||
|
shell=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
return proc.returncode, proc.stdout, proc.stderr
|
||||||
54
sys/sql_bridge.php
Normal file
54
sys/sql_bridge.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CLI bridge so sandboxed Python can call the same SQL helpers as PHP (sys.php).
|
||||||
|
* Reads JSON from stdin, writes JSON to stdout, errors to stderr.
|
||||||
|
*/
|
||||||
|
if (php_sapi_name() !== 'cli') {
|
||||||
|
fwrite(STDERR, "CLI only\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once dirname(__DIR__) . '/.env.php';
|
||||||
|
require_once __DIR__ . '/sys.php';
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://stdin'), true);
|
||||||
|
if (!is_array($input) || empty($input['action'])) {
|
||||||
|
fwrite(STDERR, json_encode(['error' => 'Invalid input']));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dbHost = $input['db_host'] ?? null;
|
||||||
|
$dbUser = $input['db_user'] ?? null;
|
||||||
|
$dbPass = $input['db_pass'] ?? null;
|
||||||
|
$dbName = $input['db_name'] ?? null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($input['action']) {
|
||||||
|
case 'sql':
|
||||||
|
$result = sql($input['query'], $dbHost, $dbUser, $dbPass, $dbName);
|
||||||
|
echo json_encode(['result' => $result]);
|
||||||
|
break;
|
||||||
|
case 'sql_read':
|
||||||
|
$result = sql_read($input['query'], $dbHost, $dbUser, $dbPass, $dbName);
|
||||||
|
echo json_encode(['result' => $result]);
|
||||||
|
break;
|
||||||
|
case 'sql_write':
|
||||||
|
$result = sql_write($input['query'], $dbHost, $dbUser, $dbPass, $dbName);
|
||||||
|
echo json_encode(['result' => $result]);
|
||||||
|
break;
|
||||||
|
case 'sql_read_and_hydrate':
|
||||||
|
$result = sql_read_and_hydrate($input['query']);
|
||||||
|
echo json_encode(['result' => $result]);
|
||||||
|
break;
|
||||||
|
case 'get_tbl_primary_key_col_name':
|
||||||
|
$result = getTblPrimaryKeyColName($input['table']);
|
||||||
|
echo json_encode(['result' => $result]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception('Unknown action: ' . $input['action']);
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
fwrite(STDERR, json_encode(['error' => $e->getMessage()]));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user