JavaScript Python PHP
import * as __biblioteca0 from "pgsql-ast-parser";
function executar(input = "INSERT INTO dados (codigo) VALUES ('00123');") {
const parse = __biblioteca0["parse"];
const locationOf = __biblioteca0["locationOf"];
function textCheck(value) {
if (value.includes('\0') || /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(value))
throw new Error('NUL e surrogates isolados não são suportados pelo subconjunto PostgreSQL UTF-8.');
}
function identifier(value) {
textCheck(value);
if (!value || new TextEncoder().encode(value).length > 63)
throw new Error('Identificador deve ter de 1 a 63 bytes UTF-8, sem truncamento.');
return '"' + value.replace(/"/g, '""') + '"';
}
function decimalKey(raw) {
const match = raw.match(/^([+-]?)(\d+)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/);
if (!match)
throw new Error('Formato numérico não suportado; use dígitos antes do ponto e valores seguros ou strings.');
let digits = (match[2] + (match[3] ?? '')).replace(/^0+/, '');
let power = Number(match[4] ?? 0) - (match[3]?.length ?? 0);
if (!digits)
return match[1] === '-' ? '-0' : '0';
while (digits.endsWith('0')) {
digits = digits.slice(0, -1);
power++;
}
return `${match[1] === '-' ? '-' : ''}${digits}e${power}`;
}
function safeNumber(raw) {
const value = Number(raw);
if (!Number.isFinite(value) || Object.is(value, -0) || (Number.isInteger(value) && !Number.isSafeInteger(value)) || decimalKey(raw) !== decimalKey(String(value)))
throw new Error('Número não preservável como JSON/JavaScript: inteiro inseguro, precisão decimal excessiva, underflow, infinito ou zero negativo. Use uma string para preservar os dígitos.');
return value;
}
function decodeString(raw) {
const escape = /^[eE]'/.test(raw);
if (escape)
throw new Error('E strings não são suportadas; use string comum com standard_conforming_strings=on.');
const start = 1;
if (raw[start - 1] !== "'" || !raw.endsWith("'"))
throw new Error('Sintaxe de string não suportada (sem dollar quoting ou concatenação).');
let value = '';
for (let index = start; index < raw.length - 1; index++) {
const character = raw[index];
if (character === "'") {
if (raw[++index] !== "'")
throw new Error('Concatenação/sintaxe de string não suportada.');
value += "'";
}
else
value += character;
}
textCheck(value);
return value;
}
function sqlToJson(input) {
if (!input.trim() || input.length > 100000)
throw new Error('Informe SQL com até 100 mil caracteres.');
textCheck(input);
let statements;
try {
statements = parse(input, { locationTracking: true });
}
catch (cause) {
throw new Error(`SQL inválido ou sintaxe não suportada: ${cause instanceof Error ? cause.message : 'erro de parsing'}`);
}
if (!statements.length)
throw new Error('Informe pelo menos um INSERT.');
const result = [];
let signature = '';
function fail(message, node) {
const offset = locationOf(node)?.start;
const before = offset === undefined ? '' : input.slice(0, offset);
throw new Error(message + (offset === undefined ? '' : ` (linha ${before.split('\n').length}, coluna ${offset - before.lastIndexOf('\n')})`));
}
function value(node) {
try {
const location = locationOf(node);
const raw = location ? input.slice(location.start, location.end) : '';
if (node.type === 'string')
return decodeString(raw);
if (node.type === 'integer' || node.type === 'numeric') {
if (!/^[+-]?\d+(?:\.\d+)?$/.test(raw))
throw new Error('Use números SQL sem expoentes, com dígitos antes/depois do ponto.');
return safeNumber(raw);
}
if (node.type === 'boolean')
return node.value;
if (node.type === 'null')
return null;
throw new Error(`Construção não suportada: ${node.type}. Aceita somente strings, números, TRUE, FALSE e NULL.`);
}
catch (cause) {
fail(cause instanceof Error ? cause.message : 'Literal não suportado.', node);
}
}
for (const statement of statements) {
if (statement.type !== 'insert')
fail(`Comando não suportado: ${statement.type}. Somente INSERT é aceito.`, statement);
if (statement.returning || statement.onConflict || statement.overriding)
fail('RETURNING, ON CONFLICT e OVERRIDING não são suportados.', statement);
if (!statement.columns?.length || statement.columns.length > 100 || statement.insert.type !== 'values' || statement.into.alias)
fail('Exige colunas explícitas (até 100) e VALUES; sem SELECT ou alias.', statement);
const table = statement.into.name.replace(/""/g, '"');
const schema = statement.into.schema?.replace(/""/g, '"');
identifier(table);
if (schema)
identifier(schema);
const columns = statement.columns.map(column => { const name = column.name.replace(/""/g, '"'); identifier(name); return name; });
if (new Set(columns).size !== columns.length)
fail('Colunas duplicadas após interpretar os identificadores.', statement);
const current = JSON.stringify([schema ?? null, table, columns]);
if (signature && signature !== current)
fail('Todos os INSERTs devem ter a mesma tabela/schema e lista ordenada de colunas.', statement);
signature = current;
for (const row of statement.insert.values) {
if (row.length !== columns.length)
fail('Quantidade de valores diferente da quantidade de colunas.', statement);
if (result.length >= 1000)
fail('Limite de 1.000 registros.', statement);
const object = Object.create(null);
row.forEach((node, index) => { object[columns[index]] = value(node); });
result.push(object);
}
}
return JSON.stringify(result, null, 2);
}
return sqlToJson(input);
}
import json
import math
import re
from decimal import Decimal
from pglast import parser
def executar(input="INSERT INTO dados (codigo) VALUES ('00123');"):
def text_check(value):
if "\0" in value or any(0xD800 <= ord(c) <= 0xDFFF for c in value):
raise ValueError("NUL e surrogates isolados não são suportados em PostgreSQL UTF-8.")
def identifier(value, config=False):
text_check(value)
if not value or len(value.encode("utf-8")) > 63: raise ValueError("Identificador deve ter de 1 a 63 bytes UTF-8.")
if config and any(item in value for item in (";","\r","\n","--","/*","*/")): raise ValueError("Tabela/schema devem ser nomes isolados, não fragmentos SQL.")
return '"' + value.replace('"','""') + '"'
def safe_number(raw, sql=False):
if sql and not re.fullmatch(r"[+-]?[0-9]+(?:\.[0-9]+)?",raw): raise ValueError("Números SQL devem ser literais sem expoentes.")
decimal = Decimal(raw)
value = float(raw)
if not math.isfinite(value) or (value == 0 and (decimal.is_signed() or decimal != 0)) or (value.is_integer() and abs(value)>9007199254740991) or Decimal(str(value)) != decimal:
raise ValueError("Número não preservável como JSON/JavaScript; use uma string.")
return int(value) if value.is_integer() else value
if not input.strip() or len(input.encode("utf-16-le",errors="surrogatepass"))//2>100000: raise ValueError("Informe SQL com até 100 mil caracteres.")
text_check(input)
# Full PostgreSQL syntax validation, then restrict the parser's tokens.
parser.parse_sql(input)
tokens=[(input[token.start:token.end+1],token.start,token.name) for token in parser.scan(input)]
index=0
def peek(): return tokens[index][0] if index<len(tokens) else ""
def fail(message):
position=tokens[index][1] if index<len(tokens) else len(input)
prefix=input[:position]
raise ValueError(f"{message} (linha {prefix.count(chr(10))+1}, coluna {len(prefix.rsplit(chr(10),1)[-1])+1}).")
def take(expected=None):
nonlocal index
if index>=len(tokens): fail("SQL incompleto.")
value=peek()
if expected is not None and value.upper()!=expected: fail("Construção não suportada: "+value+"; esperado "+expected)
index+=1
return value
def name():
if index>=len(tokens): fail("Identificador ausente.")
raw,position,kind=tokens[index]
if raw.startswith('"') and raw.endswith('"'): value=raw[1:-1].replace('""','"')
elif kind=="IDENT" or re.fullmatch(r"[A-Za-z_][A-Za-z0-9_$]*",raw): value=raw.lower()
else: fail("Identificador não suportado.")
identifier(value)
take()
return value
def literal():
raw=take()
if raw.upper() in ("TRUE","FALSE","NULL"): return {"TRUE":True,"FALSE":False,"NULL":None}[raw.upper()]
if raw.startswith("'") and raw.endswith("'"):
value=""; cursor=1
while cursor<len(raw)-1:
character=raw[cursor]
if character=="'":
cursor+=1
if cursor>=len(raw)-1 or raw[cursor]!="'": fail("Concatenação de strings não é suportada.")
value+=character; cursor+=1
text_check(value)
return value
if raw in ("+","-"): raw+=take()
try: return safe_number(raw,True)
except Exception as error: fail("Literal não suportado: "+str(error))
records=[]; signature=None
while index<len(tokens):
take("INSERT"); take("INTO"); first=name()
schema=None; table=first
if peek()==".": take("."); schema=first; table=name()
take("("); columns=[name()]
while peek()==",": take(","); columns.append(name())
take(")")
if len(columns)>100 or len(set(columns))!=len(columns): fail("Colunas duplicadas ou limite de 100 colunas.")
current=(schema,table,columns)
if signature is not None and current!=signature: fail("INSERTs devem ter mesma tabela/schema e colunas ordenadas.")
signature=current
take("VALUES")
while True:
take("("); values=[literal()]
while peek()==",": take(","); values.append(literal())
take(")")
if len(values)!=len(columns): fail("Quantidade de valores diferente das colunas.")
if len(records)>=1000: fail("Limite de 1.000 registros.")
records.append(dict(zip(columns,values)))
if peek()!=",": break
take(",")
if peek()==";": take(";")
elif index<len(tokens): fail("Construção não suportada: "+peek())
if not records: fail("Informe pelo menos um INSERT.")
return json.dumps(records,ensure_ascii=False,indent=2)
<?php
use Brick\Math\BigDecimal;
use sad_spirit\pg_builder\Lexer;
use sad_spirit\pg_builder\Token;
require_once 'vendor/autoload.php';
function executar($input = "INSERT INTO dados (codigo) VALUES ('00123');") {
$textCheck = function ($value) {
if (str_contains($value,"\0") || !mb_check_encoding($value,'UTF-8')) throw new InvalidArgumentException('NUL e UTF-8 inválido não são suportados.');
};
$identifier = function ($value,$config = false) use ($textCheck) {
$textCheck($value);
if ($value==='' || strlen($value)>63) throw new InvalidArgumentException('Identificador deve ter de 1 a 63 bytes UTF-8.');
if ($config && preg_match('/[;\r\n]|--|\/\*|\*\//',$value)) throw new InvalidArgumentException('Tabela/schema devem ser nomes isolados.');
return '"'.str_replace('"','""',$value).'"';
};
$safeNumber = function ($raw,$sql = false) {
if ($sql && !preg_match('/^[+-]?[0-9]+(?:\.[0-9]+)?$/D',$raw)) throw new InvalidArgumentException('Números SQL devem ser literais sem expoentes.');
$value=(float)$raw; $exact=BigDecimal::of(ltrim($raw,'+'));
if (!is_finite($value) || ($value==0 && (str_starts_with($raw,'-') || !$exact->isZero())) || (floor($value)===$value && abs($value)>9007199254740991) || $exact->compareTo(BigDecimal::of(json_encode($value,JSON_THROW_ON_ERROR)))!==0) throw new InvalidArgumentException('Número não preservável; use uma string.');
return floor($value)===$value ? (int)$value : $value;
};
if (trim($input)==='' || strlen(mb_convert_encoding($input,'UTF-16LE','UTF-8'))/2>100000) throw new InvalidArgumentException('Informe SQL com até 100 mil caracteres.');
$textCheck($input);
$stream=(new Lexer(['standard_conforming_strings'=>true]))->tokenize($input);
$fail=function ($message) use ($stream,$input) {
$position=$stream->getCurrent()->getPosition();
$prefix=substr($input,0,$position); $last=strrpos($prefix,"\n");
throw new InvalidArgumentException($message.' (linha '.(substr_count($prefix,"\n")+1).', coluna '.($position-($last===false ? -1 : $last)).').');
};
$peek=fn()=> $stream->isEOF() ? '' : $stream->getCurrent()->getValue();
$take=function ($expected=null) use ($stream,$peek,$fail) {
if ($stream->isEOF()) $fail('SQL incompleto.');
$value=$peek();
if ($expected!==null && strtoupper($value)!==$expected) $fail('Construção não suportada: '.$value.'; esperado '.$expected);
$stream->next();
return $value;
};
$name=function () use ($stream,$input,$identifier,$take,$fail) {
$token=$stream->getCurrent(); $position=$token->getPosition();
if (!$token->matches(Token::TYPE_IDENTIFIER) && !$token->matches(Token::TYPE_UNRESERVED_KEYWORD) && !$token->matches(Token::TYPE_COL_NAME_KEYWORD)) $fail('Identificador não suportado.');
$value=$token->getValue();
if (($input[$position] ?? '')!=='"') $value=strtolower($value);
$identifier($value); $take();
return $value;
};
$literal=function () use ($stream,$input,$take,$peek,$safeNumber,$textCheck,$fail) {
$token=$stream->getCurrent(); $position=$token->getPosition();
if ($token->matches(Token::TYPE_STRING)) {
if (($input[$position] ?? '')!=="'") $fail('Use strings comuns; sem E strings ou dollar quoting.');
$cursor=$position+1;
while ($cursor<strlen($input)) {
if ($input[$cursor]!=="'") { $cursor++; continue; }
if (($input[$cursor+1] ?? '')==="'") { $cursor+=2; continue; }
$cursor++; break;
}
$between=substr($input,$cursor,$stream->look()->getPosition()-$cursor);
if (!preg_match('~\A(?:\s|--[^\r\n]*(?:\r\n|\n|$)|/\*(?:(?!\*/).)*\*/)*\z~s',$between)) $fail('Concatenação de strings não é suportada.');
$value=$take(); $textCheck($value); return $value;
}
$raw=$take(); $upper=strtoupper($raw);
if ($upper==='NULL') return null;
if ($upper==='TRUE' || $upper==='FALSE') return $upper==='TRUE';
if ($raw==='+' || $raw==='-') $raw.=$take();
try { return $safeNumber($raw,true); }
catch (Throwable $error) { $fail('Literal não suportado: '.$error->getMessage()); }
};
$records=[]; $signature=null;
while (!$stream->isEOF()) {
$take('INSERT'); $take('INTO'); $first=$name(); $schema=null; $table=$first;
if ($peek()==='.') { $take('.'); $schema=$first; $table=$name(); }
$take('('); $columns=[$name()];
while ($peek()===',') { $take(','); $columns[]=$name(); }
$take(')');
if (count($columns)>100 || count(array_unique($columns))!==count($columns)) $fail('Colunas duplicadas ou limite de 100 colunas.');
$current=[$schema,$table,$columns];
if ($signature!==null && $signature!==$current) $fail('INSERTs devem ter mesma tabela/schema e lista ordenada de colunas.');
$signature=$current; $take('VALUES');
do {
$take('('); $values=[$literal()];
while ($peek()===',') { $take(','); $values[]=$literal(); }
$take(')');
if (count($values)!==count($columns)) $fail('Quantidade de valores diferente das colunas.');
if (count($records)>=1000) $fail('Limite de 1.000 registros.');
$row=new stdClass(); foreach ($columns as $index=>$column) $row->{$column}=$values[$index];
$records[]=$row; $more=$peek()===',';
if ($more) $take(',');
} while ($more);
if ($peek()===';') $take(';');
elseif (!$stream->isEOF()) $fail('Construção não suportada: '.$peek());
}
if (!$records) $fail('Informe pelo menos um INSERT.');
return preg_replace_callback('/^( +)/m',fn($match)=>str_repeat(' ',strlen($match[1])/2),json_encode($records,JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR));
}