isUnsigned || $right->isUnsigned) { throw new InvalidArgumentException("Only cast uint from signed int is supported"); } $funcName = "{$left->pgName}_from_{$right->pgName}"; $fn = "PG_FUNCTION_INFO_V1($funcName);\n"; $fn .= "Datum $funcName(PG_FUNCTION_ARGS) {\n"; $fn .= " $right->name a = $right->pgGetArgMacro(0);\n"; $fn .= " if (a < 0) {\n"; $fn .= " OUT_OF_RANGE_ERR($left->pgName);\n"; $fn .= " }\n"; $fn .= "\n"; // Check for overflow when casting wider integer types than receiver type if ($right->bitSize > $left->bitSize) { $fn .= " if (a > {$left->getMaxConstC()}) {\n"; $fn .= " OUT_OF_RANGE_ERR($left->pgName);\n"; $fn .= " }\n"; } $fn .= " $left->pgReturnMacro(($left->name) a);\n"; $fn .= "}\n"; return $fn; } function getCastIntFromUIntFunc(Type $left, Type $right): string { if ($left->isUnsigned || !$right->isUnsigned) { throw new InvalidArgumentException("Only cast signed int from uint is supported"); } $funcName = "{$left->pgName}_from_{$right->pgName}"; $fn = "PG_FUNCTION_INFO_V1($funcName);\n"; $fn .= "Datum $funcName(PG_FUNCTION_ARGS) {\n"; $fn .= " $right->name a = $right->pgGetArgMacro(0);\n"; $fn .= "\n"; // Check for overflow when casting wider integer types than receiver type if ($right->bitSize >= $left->bitSize) { $fn .= " if (a > {$left->getMaxConstC()}) {\n"; $fn .= " OUT_OF_RANGE_ERR($left->pgName);\n"; $fn .= " }\n"; } $fn .= " $left->pgReturnMacro(($left->name) a);\n"; $fn .= "}\n"; return $fn; } function getCastIntFromIntFunc(Type $left, Type $right): string { if ($left->isUnsigned || $right->isUnsigned) { throw new InvalidArgumentException("Only cast signed int from signed int is supported"); } $funcName = "{$left->pgName}_from_{$right->pgName}"; $fn = "PG_FUNCTION_INFO_V1($funcName);\n"; $fn .= "Datum $funcName(PG_FUNCTION_ARGS) {\n"; $fn .= " $right->name a = $right->pgGetArgMacro(0);\n"; $fn .= "\n"; // Check for overflow when casting wider integer types than receiver type if ($right->bitSize > $left->bitSize) { $fn .= " if (a > {$left->getMaxConstC()} || a < {$left->getMinConstC()}) {\n"; $fn .= " OUT_OF_RANGE_ERR($left->pgName);\n"; $fn .= " }\n"; } $fn .= " $left->pgReturnMacro(($left->name) a);\n"; $fn .= "}\n"; return $fn; } function getCastUIntFromUIntFunc(Type $left, Type $right): string { if (!$left->isUnsigned || !$right->isUnsigned) { throw new InvalidArgumentException("Only cast uint from uint is supported"); } $funcName = "{$left->pgName}_from_{$right->pgName}"; $fn = "PG_FUNCTION_INFO_V1($funcName);\n"; $fn .= "Datum $funcName(PG_FUNCTION_ARGS) {\n"; $fn .= " $right->name a = $right->pgGetArgMacro(0);\n"; $fn .= "\n"; // Check for overflow when casting wider integer types than receiver type if ($right->bitSize > $left->bitSize) { $fn .= " if (a > {$left->getMaxConstC()}) {\n"; $fn .= " OUT_OF_RANGE_ERR($left->pgName);\n"; $fn .= " }\n"; } $fn .= " $left->pgReturnMacro(($left->name) a);\n"; $fn .= "}\n"; return $fn; } function getCastUIntToIntFunc(Type $left, Type $right): string { if (!$left->isUnsigned || $right->isUnsigned) { throw new InvalidArgumentException("Only cast uint to signed int is supported"); } $funcName = "{$left->pgName}_to_{$right->pgName}"; $fn = "PG_FUNCTION_INFO_V1($funcName);\n"; $fn .= "Datum $funcName(PG_FUNCTION_ARGS) {\n"; $fn .= " $left->name a = $left->pgGetArgMacro(0);\n"; $fn .= "\n"; // Check for overflow when casting wider integer types than receiver type if ($left->bitSize >= $right->bitSize) { $fn .= " if (a > {$right->getMaxConstC()}) {\n"; $fn .= " OUT_OF_RANGE_ERR($right->pgName);\n"; $fn .= " }\n"; } $fn .= " $right->pgReturnMacro(($right->name) a);\n"; $fn .= "}\n"; return $fn; } function getCastIntToUIntFunc(Type $left, Type $right): string { if ($left->isUnsigned || !$right->isUnsigned) { throw new InvalidArgumentException("Only cast signed int to uint is supported"); } $funcName = "{$left->pgName}_to_{$right->pgName}"; $fn = "PG_FUNCTION_INFO_V1($funcName);\n"; $fn .= "Datum $funcName(PG_FUNCTION_ARGS) {\n"; $fn .= " $left->name a = $left->pgGetArgMacro(0);\n"; $fn .= "\n"; $fn .= " if (a < 0) {\n"; $fn .= " OUT_OF_RANGE_ERR($right->pgName);\n"; $fn .= " }\n"; // Check for overflow when casting wider integer types than receiver type if ($left->bitSize > $right->bitSize) { $fn .= " if (a > {$right->getMaxConstC()}) {\n"; $fn .= " OUT_OF_RANGE_ERR($right->pgName);\n"; $fn .= " }\n"; } $fn .= " $right->pgReturnMacro(($right->name) a);\n"; $fn .= "}\n"; return $fn; } function getCastIntToIntFunc(Type $left, Type $right): string { if ($left->isUnsigned || $right->isUnsigned) { throw new InvalidArgumentException("Only cast signed int to signed int is supported"); } $funcName = "{$left->pgName}_to_{$right->pgName}"; $fn = "PG_FUNCTION_INFO_V1($funcName);\n"; $fn .= "Datum $funcName(PG_FUNCTION_ARGS) {\n"; $fn .= " $left->name a = $left->pgGetArgMacro(0);\n"; $fn .= "\n"; // Check for overflow when casting wider integer types than receiver type if ($left->bitSize > $right->bitSize) { $fn .= " if (a > {$right->getMaxConstC()} || a < {$right->getMinConstC()}) {\n"; $fn .= " OUT_OF_RANGE_ERR($right->pgName);\n"; $fn .= " }\n"; } $fn .= " $right->pgReturnMacro(($right->name) a);\n"; $fn .= "}\n"; return $fn; } function getCastUIntToUIntFunc(Type $left, Type $right): string { if (!$left->isUnsigned || !$right->isUnsigned) { throw new InvalidArgumentException("Only cast uint to uint is supported"); } $funcName = "{$left->pgName}_to_{$right->pgName}"; $fn = "PG_FUNCTION_INFO_V1($funcName);\n"; $fn .= "Datum $funcName(PG_FUNCTION_ARGS) {\n"; $fn .= " $left->name a = $left->pgGetArgMacro(0);\n"; $fn .= "\n"; // Check for overflow when casting wider integer types than receiver type if ($left->bitSize > $right->bitSize) { $fn .= " if (a > {$right->getMaxConstC()}) {\n"; $fn .= " OUT_OF_RANGE_ERR($right->pgName);\n"; $fn .= " }\n"; } $fn .= " $right->pgReturnMacro(($right->name) a);\n"; $fn .= "}\n"; return $fn; } function getCastToJSONFunc(Type $left): string { $right = JSON; $funcName = "{$left->pgName}_to_{$right->pgName}"; return <<name a = $left->pgGetArgMacro(0); char buf[{$left->getStrBufLenConstC()}]; char *bufPtr = {$left->name}_to_string(a, buf, sizeof(buf)); /* json type in Postgres is really just text with json input cast */ Datum result = DirectFunctionCall1(json_in, CStringGetDatum(bufPtr)); $right->pgReturnMacro(result); } C; } function getCastFromNumeric(Type $left): string { $right = NUMERIC; $funcName = "{$left->pgName}_from_{$right->pgName}"; // Fast path for small integers if ($left->bitSize < 64) { /** * @var string $numericFnName * @var Type $intTyp */ if ($left->isUnsigned) { [$numericFnName, $intTyp] = match ($left->bitSize) { 8 => ['numeric_int2', INT16], 16 => ['numeric_int4', INT32], 32 => ['numeric_int8', INT64], }; } else { [$numericFnName, $intTyp] = match ($left->bitSize) { 8 => ['numeric_int2', INT16], }; } return <<name numInt = 0; // Fast path for small integers numInt = $intTyp->fromDatum(DirectFunctionCall1($numericFnName, NumericGetDatum(num))); if (numInt < $left->minValue || numInt > $left->maxValue) { OUT_OF_RANGE_ERR($left->pgName); } $left->pgReturnMacro(($left->name)numInt); } C; } return ''; } function getCastToNumeric(Type $left): string { $right = NUMERIC; $funcName = "{$left->pgName}_to_{$right->pgName}"; // Fast path for small integers if ($left->bitSize < 64) { /** * @var string $numericFnName * @var Type $intTyp */ if ($left->isUnsigned) { [$numericFnName, $intTyp] = match ($left->bitSize) { 8 => ['int2_numeric', INT16], 16 => ['int4_numeric', INT32], 32 => ['int8_numeric', INT64], }; } else { [$numericFnName, $intTyp] = match ($left->bitSize) { 8 => ['int2_numeric', INT16], }; } return <<name val = $left->pgGetArgMacro(0); PG_RETURN_DATUM(DirectFunctionCall1($numericFnName, $intTyp->toDatum(($intTyp->name)val))); } C; } return ''; } function getCastToJSONBFunc(Type $left): string { $right = JSONB; $funcName = "{$left->pgName}_to_{$right->pgName}"; // Fast path for small integers if ($left->bitSize < 64) { /** * @var string $numericFnName * @var Type $intTyp */ if ($left->isUnsigned) { [$numericFnName, $intTyp] = match ($left->bitSize) { 8 => ['int2_numeric', INT16], 16 => ['int4_numeric', INT32], 32 => ['int8_numeric', INT64], }; } else { [$numericFnName, $intTyp] = match ($left->bitSize) { 8 => ['int2_numeric', INT16], }; } $numericTyp = NUMERIC; return <<name val = $left->pgGetArgMacro(0); JsonbValue jbv; Jsonb* result; Numeric num = $numericTyp->fromDatum(DirectFunctionCall1($numericFnName, $intTyp->toDatum(($intTyp->name)val))); /* convert Numeric to JsonbValue */ jbv.type = jbvNumeric; jbv.val.numeric = num; /* wrap into a Jsonb container */ result = JsonbValueToJsonb(&jbv); $right->pgReturnMacro(result); } C; } return <<name val = $left->pgGetArgMacro(0); JsonbValue jbv; Jsonb* result; /* convert to Numeric */ char buf[{$left->getStrBufLenConstC()}]; Numeric num = {$left->name}_to_numeric(val, buf, sizeof(buf)); /* convert Numeric to JsonbValue */ jbv.type = jbvNumeric; jbv.val.numeric = num; /* wrap into a Jsonb container */ result = JsonbValueToJsonb(&jbv); $right->pgReturnMacro(result); } C; } function getCastFromJSONBFunc(Type $left): string { $right = JSONB; $funcName = "{$left->pgName}_from_{$right->pgName}"; return <<name retValue = 0; if (!JsonbExtractScalar(&in->root, &v)) cannotCastJsonbValue(v.type, "{$left->pgName}"); if (v.type == jbvNull) { PG_FREE_IF_COPY(in, 0); PG_RETURN_NULL(); } if (v.type != jbvNumeric) cannotCastJsonbValue(v.type, "{$left->pgName}"); // Numeric string cStrValue = DatumGetCString( DirectFunctionCall1( numeric_out, NumericGetDatum(v.val.numeric) ) ); convRes = parse_{$left->name}(cStrValue, &retValue); if (convRes == ParseOK) { pfree(cStrValue); PG_FREE_IF_COPY(in, 0); $left->pgReturnMacro(retValue); } if (convRes == ParseError) { ereport( ERROR, ( errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type {$left->pgName}: \"%s\"", cStrValue) ) ); } OUT_OF_RANGE_ERR($left->pgName); } C; } function getCastFromJSONFunc(Type $left): string { $right = JSON; $funcName = "{$left->pgName}_from_{$right->pgName}"; return <<name retValue = 0; parse_uint_res_t convRes; // Enforce JSON validation for supported PostgreSQL versions #if PG_VERSION_NUM >= 130000 JsonTokenType token; #if PG_VERSION_NUM >= 170000 // 17+ /* Lex one token to check JSON type */ JsonLexContext lex; JsonParseErrorType lexResult; makeJsonLexContext(&lex, json, false); lexResult = json_lex(&lex); if (lexResult != JSON_SUCCESS) json_errsave_error(lexResult, &lex, NULL); token = lex.token_type; #elif PG_VERSION_NUM >= 160000 // 16 JsonParseErrorType result; JsonLexContext *lex = makeJsonLexContext(json, false); /* Lex exactly one token from the input and check its type. */ result = json_lex(lex); if (result != JSON_SUCCESS) json_errsave_error(result, lex, NULL); token = lex->token_type; #else // 13-15 JsonLexContext *lex; JsonParseErrorType result; lex = makeJsonLexContext(json, false); /* Lex exactly one token from the input and check its type. */ result = json_lex(lex); if (result != JSON_SUCCESS) json_ereport_error(result, lex); token = lex->token_type; #endif if (token == JSON_TOKEN_NULL) { PG_FREE_IF_COPY(json, 0); PG_RETURN_NULL(); } if (token != JSON_TOKEN_NUMBER) { PG_FREE_IF_COPY(json, 0); cannotCastJsonValue(token, "$left->pgName"); } #endif /* Convert the text to C string */ cStrValue = text_to_cstring(json); convRes = parse_{$left->name}(cStrValue, &retValue); pfree(cStrValue); PG_FREE_IF_COPY(json, 0); if (convRes == ParseOK) { $left->pgReturnMacro(retValue); } if (convRes == ParseError) { ereport( ERROR, ( errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type $left->pgName") ) ); } OUT_OF_RANGE_ERR($left->pgName); } C; } $header = <<bitSize < 64) { $buf .= "// Numeric casts\n\n"; $buf .= getCastFromNumeric($LEFT_TYPE) . "\n\n"; $buf .= getCastToNumeric($LEFT_TYPE) . "\n\n"; } $buf .= "// JSON casts\n\n"; $buf .= getCastToJSONFunc($LEFT_TYPE) . "\n\n"; $buf .= getCastToJSONBFunc($LEFT_TYPE) . "\n\n"; $buf .= getCastFromJSONFunc($LEFT_TYPE) . "\n\n"; $buf .= getCastFromJSONBFunc($LEFT_TYPE) . "\n\n"; file_put_contents("casts/{$LEFT_TYPE->name}.c", $buf); echo "casts/{$LEFT_TYPE->name}.c successfully generated\n"; } /** * @var Type $LEFT_TYPE */ foreach (INT_TYPES as $LEFT_TYPE) { $buf = $header; $buf .= "\n// Signed comparison\n\n"; foreach (INT_TYPES as $RIGHT_TYPE) { if ($RIGHT_TYPE === $LEFT_TYPE) { continue; } $buf .= getCastIntFromIntFunc($LEFT_TYPE, $RIGHT_TYPE) . "\n\n"; $buf .= getCastIntToIntFunc($LEFT_TYPE, $RIGHT_TYPE) . "\n\n"; } $buf .= "// Unsigned comparison\n\n"; foreach (UINT_TYPES as $RIGHT_TYPE) { $buf .= getCastIntFromUIntFunc($LEFT_TYPE, $RIGHT_TYPE) . "\n\n"; $buf .= getCastIntToUIntFunc($LEFT_TYPE, $RIGHT_TYPE) . "\n\n"; } if (in_array($LEFT_TYPE, CUSTOM_INT_TYPES, true)) { if ($LEFT_TYPE->bitSize < 64) { $buf .= "// Numeric casts\n\n"; $buf .= getCastFromNumeric($LEFT_TYPE) . "\n\n"; $buf .= getCastToNumeric($LEFT_TYPE) . "\n\n"; } $buf .= "// JSON casts\n\n"; $buf .= getCastToJSONFunc($LEFT_TYPE) . "\n\n"; $buf .= getCastToJSONBFunc($LEFT_TYPE) . "\n\n"; $buf .= getCastFromJSONFunc($LEFT_TYPE) . "\n\n"; $buf .= getCastFromJSONBFunc($LEFT_TYPE) . "\n\n"; } file_put_contents("casts/{$LEFT_TYPE->name}.c", $buf); echo "casts/{$LEFT_TYPE->name}.c successfully generated\n"; }