Skip to main content

PostgreSQL Compatibility

SereneDB implements the SQL dialect of PostgreSQL. Not every PostgreSQL statement parses, and not all underlying functionality is implemented. Among the statements that fail to parse are SAVEPOINT, DECLARE ... CURSOR and FETCH, SELECT ... FOR UPDATE, FETCH FIRST ... WITH TIES, identity columns, CREATE DOMAIN, unlogged tables, materialized views, CREATE EXTENSION, CREATE INDEX CONCURRENTLY and PL/pgSQL function bodies; SQL-language functions work. LISTEN, NOTIFY and UNLISTEN parse and answer that they are not supported.

This page gives a non-exhaustive overview of supported core SQL functionality, followed by the behavioral differences where SereneDB intentionally diverges from PostgreSQL.

For PostgreSQL-specific functionality such as system table support, see the System Table Compatibility page.

Data Definition​

Table Creation & Deletion​

FeatureSupport StateDetails
CREATE TABLEYes
DROP TABLEYes
Default ValuesYes
GENERATEDYes
Check ConstraintsYes
Not-Null ConstraintsYes
Unique ConstraintsYes
Primary KeysYes
Foreign KeysYesEnforced; over-eager on some UPDATEs
Named ConstraintsYes
Exclusion ConstraintsNoSee issue
System ColumnsPartialEvery table has tableoid, ctid, xmin, xmax, cmin and cmax. ctid identifies a row and orders rows by their position, but its block and offset don't name storage pages, and the row a ctid names can move after VACUUM or a checkpoint. xmin, xmax, cmin and cmax are always 0.

Table Modification (ALTER TABLE)​

FeatureSupport StateDetails
ADD COLUMNYesA serial column can't declare PRIMARY KEY or UNIQUE on a table that already has rows
DROP COLUMNYes
ADD CHECKYes
ADD CONSTRAINTYesCHECK, UNIQUE and PRIMARY KEY constraints; not FOREIGN KEY
ADD FOREIGN KEYNoFails with No support for adding FOREIGN_KEY constraints with ALTER TABLE
DROP CONSTRAINTYes
ALTER COLUMNYesTYPE, SET/DROP DEFAULT and SET NOT NULL
SET DEFAULTYes
DROP DEFAULTYes
COLUMN TYPEYesNot on a column that has a CHECK constraint
RENAME COLUMNYes
RENAME TOYes
SET SCHEMAPartialMoves a table, a search table included, with its indexes and owned sequences, and with ALTER VIEW, ALTER SEQUENCE, ALTER TYPE, ALTER FUNCTION and ALTER TEXT SEARCH DICTIONARY a view, sequence, type, function or dictionary; not an entry that another object depends on

Privileges​

FeatureSupport StateDetails
CREATE ROLEYesSee Database Roles
OWNER TOYesTables, views, sequences, schemas, types, jobs; not databases or functions
ALTER ROLEYesAttributes, passwords, VALID UNTIL, RENAME TO
GRANTYesPrivileges and role membership, incl. column grants; see Privileges
REVOKEYesIncl. GRANT OPTION FOR
SET ROLEYesEnforced; membership required for non-superusers
INHERITYesINHERIT/NOINHERIT attributes enforced
ALTER DEFAULT PRIVILEGESYes
Row Security PoliciesNoSee issue

Indexes​

FeatureSupport StateDetails
CREATE INDEXYes
GINNo
BRINNo
Multicolumn IndexesYes
Ordered IndexesYes
Unique IndexesYes
Indexes on ExpressionsYes
Partial IndexesPartialInverted indexes honor WHERE. A plain index accepts it but filters only the rows present when it is built: rows written later are indexed whether or not they match, keyed on the value and on whether the predicate holds, so a partial UNIQUE index also rejects duplicates outside its predicate
INCLUDEPartialInverted indexes store INCLUDE columns. A plain index accepts INCLUDE but keeps only its key columns, so a UNIQUE index is unique on the keys alone, as in PostgreSQL

Misc​

FeatureSupport StateDetails
CREATE SCHEMAYes
DROP SCHEMAYes
search_pathYes
Table InheritanceNo
Table PartitioningNo
Foreign Data WrappersNo
ViewsYes
DatabasesYes
Scheduled JobsYesSereneDB extension, not PostgreSQL syntax: CREATE JOB
Functions & ProceduresYes
Custom TypesYes
TriggersNo
Prepared StatementsYes
Advisory LocksNo

Data Manipulation​

FeatureSupport StateDetails
INSERTYes
UPDATEYes
DELETEYes
TRUNCATEYes
RETURNINGYes
COPY FROMYes
COPY TOYes
ON CONFLICTPartial

Queries​

FeatureSupport StateDetails
Table & View ReferencesYes
Inner JoinsYes
Outer JoinsYes
SemijoinsYes
AntijoinsYes
Table FunctionsYes
Column Definition ListsNo
Lateral SubqueriesYes
User-Specified AliasesYes
GROUP BYYes
HAVINGYes
GROUPING SETSYes
CUBEYes
ROLLUPYes
WINDOW FunctionsYes
WITHYes
WITH RECURSIVEYes
UNIONYes
UNION ALLYes
INTERSECTYes
INTERSECT ALLYes
EXCEPTYes
EXCEPT ALLYes
ORDER BYYes
LIMITYes
OFFSETYes
Table Generating FunctionYes

Data Types​

Types​

Data Type Compatibility lists every PostgreSQL type and how SereneDB supports it.

Operators & Functions​

Logical​

FeatureSupport StateDetails
ANDYes
ORYes
NOTYes

Comparison​

FeatureSupport StateDetails
<Yes
>Yes
<=Yes
>=Yes
=Yes
<>Yes
!=Yes
BETWEENYes
NOT BETWEENYes
IS DISTINCTYes
IS NOT DISTINCTYes
IS NULLYes
IS NOT NULLYes
IS TRUEYes
IS NOT TRUEYes
IS FALSEYes
IS NOT FALSEYes
IS UNKNOWNYes
IS NOT UNKNOWNYes

Mathematical​

FeatureSupport StateDetails
+Yes
-Yes
*Yes
/YesOn numeric, returns double precision
%Yes
^YesOn numeric, returns double precision
/Yes
/
@Yes
&Yes
Yes
#Yes
~Yes
<<Yes
>>Yes
absYes
cbrtYes
ceilYes
degreesYes
divYes
erfYes
erfcYes
expYesOn numeric, returns double precision
factorialYes
floorYes
gcdYes
lcmYes
lnYesOn numeric, returns double precision
logYesOn numeric, returns double precision
log10YesOn numeric, returns double precision
min_scaleYes
modYes
piYes
powerYesOn numeric, returns double precision
radiansYes
roundYes
scaleYesThe scale of the column type: numeric without a scale has ten fractional digits
signYesReturns smallint
sqrtYesOn numeric, returns double precision
trim_scalePartialTrims a constant; a column's value keeps the scale of its type
truncYes
width_bucketYes
randomYes
random_normalYes
setseedYes
acosYes
acosdYes
asinYes
asindYes
atanYes
atandYes
atan2Yes
atan2dYes
cosYes
cosdYes
cotYes
cotdYes
sinYes
sindYes
tanYes
tandYes
sinhYes
coshYes
tanhYes
asinhYes
acoshYes
atanhYes

Text​

FeatureSupport StateDetails
||Yes
btrimYesWithout characters to remove, also removes Unicode space separators such as the no-break space; PostgreSQL removes only spaces
bit_lengthYes
char_lengthYes
lowerYes
lpadYes
ltrimYesSame as btrim
normalizePartialReturns the text unchanged: no normalization form is applied
octet_lengthYes
overlayYes
positionYes
rpadYes
rtrimYesSame as btrim
substringYes
trimYesSame as btrim
upperYes
^@Yes
asciiYes
casefoldYes
chrYes
concatYes
concat_wsYes
formatYes
initcapYes
leftYes
lengthYes
md5Yes
parse_identYes
quote_identYes
quote_literalYes
quote_nullableYes
repeatYes
replaceYes
reverseYes
rightYes
split_partYes
starts_withYes
string_to_arrayYes
string_to_tableYes
strposYes
substrYes
to_asciiNo
to_hexYes
translateYes
unistrYes

Bytea​

FeatureSupport StateDetails
||Yes
bit_lengthYes
btrimYes
ltrimYes
octet_lengthYes
overlayYes
positionYes
rtrimYes
substringYes
trimYes
bit_countNo
crc32Yes
crc32cYes
get_bitYes
get_byteYes
lengthYes
md5Yes
set_bitYes
set_byteYes
sha224Yes
sha384Yes
sha512Yes
substrYes
convertNo
convert_fromPartialConverts from UTF8, SQL_ASCII and LATIN1; any other valid encoding raises default conversion function ... does not exist
convert_toPartialConverts to UTF8, SQL_ASCII and LATIN1; any other valid encoding raises default conversion function ... does not exist
encodeYes
decodeYes

Bit​

FeatureSupport StateDetails
||Yes
&Yes
|Yes
#Yes
~Yes
<<Yes
>>Yes
bit_countYes
bit_lengthYes
lengthYes
octet_lengthYes
overlayNo
positionNo
substringNo
get_bitYes
set_bitYes

Pattern Matching​

FeatureSupport StateDetails
LIKEYes
SIMILAR TOYes
regexp_countYes
regexp_instrYes
regexp_likeYes
regexp_matchYes
regexp_matchesPartial
regexp_replacePartialThe start and N arguments are not supported
regexp_split_to_arrayYes
regexp_split_to_tableYes
regexp_substrYes

Data Type Formatting​

FeatureSupport StateDetails
to_charYesTZ prints the UTC offset for zones other than UTC and GMT
to_dateYes
to_numberYesThe scale of the result is fixed per query
to_timestampYesTZ input recognizes UTC, GMT and numeric offsets

Date/Time​

FeatureSupport StateDetails
+Yes
-Yes
*Yes
/Yes
ageYes
clock_timestampYes
pg_sleep_forYesAlso pg_sleep_until
current_dateYes
current_timeYes
current_timestampYes
date_addYespossible with +
date_binYes
date_partYes
date_subtractYes
date_truncYes
extractYespg_catalog.extract(field, source) returns numeric. An infinite source gives NULL where PostgreSQL gives Infinity, julian of a timestamp has 20 fractional digits, and a field that is not a constant gives six fractional digits
isfiniteYes
justify_daysYes
justify_hoursYes
justify_intervalYes
localtimeYes
localtimestampYes
make_dateYes
make_intervalYes
make_timeYes
make_timestampYes
make_timestamptzYesWith time zone name
nowYes
statement_timestampYes
timeofdayYes
timestampYesAlso timestamptz: the casts as functions, and timestamp(value, precision) rounds to the precision
transaction_timestampYes
to_timestampYes
OVERLAPSYes
EXTRACTYes

Enum​

FeatureSupport StateDetails
enum_firstYes
enum_lastYes
enum_rangeYes

Geometric​

An operator with a string literal on one side, as in point '(1,2)' <-> '(3,4)', is an error when the operator takes several types on that side: cast the literal, as in point '(1,2)' <-> point '(3,4)'. For spatial data, SereneDB also has a GEOMETRY type and ST_* functions: see Spatial Functions and the GEOMETRY type.

FeatureSupport StateDetails
+Yes
-Yes
*Yes
/Yes
@-@Yes
@@Yes
#Yes
##Yes
<->Yes
@>Yes
<@Yes
&&Yes
<<Yes
>>Yes
&<Yes
&>Yes
<<|Yes
|>>Yes
&<|Yes
|&>Yes
<^Yes
>^Yes
?#Yes
?-PartialBetween two values only: a leading ? is a parameter, so ?- x is written line_horizontal(x) or lseg_horizontal(x)
?|YesA ? parameter followed by | reads as this operator, so a parameter's bitwise or is written $1 | x or (?) | x
?-|Yes
?||Yes
~=Yes
areaYes
centerYes
diagonalYes
diameterYes
heightYes
isclosedYes
isopenYes
lengthYes
npointsYes
pcloseYes
popenYes
radiusYes
slopeYes
widthYes
boxYes
bound_boxYes
circleYes
lineYes
lsegYes
pathYes
pointYes
polygonYes

Network​

FeatureSupport StateDetails
<<Yes
<<=Yes
>>Yes
>>=Yes
&&Yes
~Yes
&Yes
|Yes
+Yes
-Yes
abbrevYes
broadcastYes
familyYes
hostYes
hostmaskYes
inet_mergeYes
inet_same_familyYes
masklenYes
netmaskYes
networkYes
set_masklenYes
textNo
FeatureSupport StateDetails
@@YesSereneDB full-text search (inverted index), not PG tsvector/tsquery
||YesSereneDB full-text search (inverted index), not PG tsvector/tsquery
&&YesSereneDB full-text search (inverted index), not PG tsvector/tsquery
!!YesSereneDB full-text search (inverted index), not PG tsvector/tsquery
<->No
@>No
<@No
array_to_tsvectorNo
get_current_ts_configNo
lengthNo
numnodeNo
plainto_tsqueryYesSereneDB full-text search (inverted index), not PG tsvector/tsquery
phraseto_tsqueryYesSereneDB full-text search (inverted index), not PG tsvector/tsquery
websearch_to_tsqueryYesSereneDB full-text search (inverted index), not PG tsvector/tsquery
querytreeNo
setweightNo
stripNo
to_tsqueryYesSereneDB full-text search (inverted index), not PG tsvector/tsquery
to_tsvectorNo
json(b)_to_tsvectorNo
ts_deleteNo
ts_filterNo
ts_headlineNo
ts_rankNo
ts_rank_cdNo
ts_rewriteNo
tsquery_phraseYesSereneDB full-text search (inverted index), not PG tsvector/tsquery
tsvector_to_arrayNo
unnestNo
ts_debugNo
ts_lexizeYesSereneDB extension: works with CREATE TEXT SEARCH DICTIONARY
ts_parseNo
ts_token_typeNo
ts_statNo

UUID​

FeatureSupport StateDetails
get_random_uuidYes
uuid_extract_timestampYes
uuid_extract_versionYes

XML​

FeatureSupport StateDetails
xmltextYes
xmlcommentYes
xmlconcatYes
xmlelementNo
xmlforestNo
xmlpiNo
xmlrootNo
xmlaggYes
IS DOCUMENTYes
IS NOT DOCUMENTYes
XMLEXISTSNo
xml_is_well_formedYesAlso xml_is_well_formed_document and xml_is_well_formed_content
xpathYes
xpath_existsYes
XMLTABLENo

JSON​

Most of the PostgreSQL json functions below are supported. SereneDB also provides JSON access through the JSON column type and SereneDB-style functions -- json_extract, json_extract_string, json_keys, json_type, json_valid and more (see JSON Functions). The PostgreSQL accessor operators ->, ->>, #> and #>> are supported.

The jsonb type is a json value that keeps its text as written (see jsonb); the jsonb_* functions and the jsonb-only operators are unavailable. A query parameter a client sends as jsonb is read as JSON.

FeatureSupport StateDetails
->Yes
->>Yes
#>Yes
#>>Yes
@>NoUse json_contains()
<@NoUse json_contains()
?NoUse json_exists()
?|NoUse json_exists()
?&NoUse json_exists()
||Yes
-No
#-No
@?NoUse json_exists()
@@NoNo JSON-path predicate
to_jsonYes
to_jsonbYesReturns json, as to_json does
array_to_jsonYes
json_arrayYes
row_to_jsonYes
json_build_arrayYesSame as json_array()
jsonb_build_arrayNo
json_build_objectYesSame as json_object(key, value)
jsonb_build_objectNo
json_objectPartialTwo text arguments are one key, value pair, as for json_build_object: the arrays of json_object('{a,b}', '{1,2}') need ::text[]
jsonb_objectNo
IS JSONNoUse json_valid()
json_array_elementsYes
jsonb_array_elementsNo
json_array_elements_textYes
jsonb_array_elements_textNo
json_array_lengthYes
jsonb_array_lengthNoUse json_array_length
json_eachYes
jsonb_eachNo
json_each_textYes
jsonb_each_textNo
json_object_keysYes
jsonb_object_keysNo
json_populate_recordYes
jsonb_populate_recordNo
json_populate_recordsetYes
jsonb_populate_recordsetNo
json_to_recordNoUse json_populate_record
jsonb_to_recordNo
json_to_recordsetNoUse json_populate_recordset
jsonb_to_recordsetNo
jsonb_setNo
jsonb_set_laxNo
jsonb_insertNo
json_strip_nullsYes
jsonb_strip_nullsNoUse json_strip_nulls
json_extract_pathYes
json_extract_path_textYes
json_pathNoNo jsonpath type

Sequence Manipulation​

FeatureSupport StateDetails
nextvalYes
setvalYes
currvalYes
lastvalYes

Conditional​

FeatureSupport StateDetails
CASEYes
COALESCEYes
NULLIFYes
GREATESTYes
LEASTYes

Array​

FeatureSupport StateDetails
@>Yes
<@Yes
&&Yes
||YesNot for multidimensional
array_appendYes
array_catYes
array_dimsYes
array_fillYesLower bounds must be 1; dimensions taken from a column fill one-dimensional arrays unless written as ARRAY[...]; up to 6 dimensions
array_lowerYes
array_ndimsYes
array_positionYes
array_positionsYes
array_prependYes
array_removeYes
array_replaceYes
array_sampleYes
array_shuffleYes
array_to_stringYes
array_upperYes
array_cardinalityYesvia cardinality()
trim_arrayYes
unnestYes

Range​

FeatureSupport StateDetails
@>YesA string literal on the right is read as an element, not a range or multirange: write r @> '[2,3)'::int4range to test a range
<@YesA string literal on the left is read as an element, not a range or multirange: write '[2,3)'::int4range <@ r to test a range
&&Yes
<<Yes
>>Yes
&<Yes
&>Yes
-|-Yes
+Yes
*Yes
-Yes
lowerYes
upperYes
isemptyYes
lower_incYes
upper_incYes
lower_infYes
upper_infYes
range_mergeYes
multirangeYes
unnestYes
CREATE TYPE ... AS RANGENo

Aggregate Functions​

Generic​
FeatureSupport StateDetails
any_valueYesAggregate functions
array_aggYesAggregate functions
avgYesAggregate functions
bit_andYesAggregate functions
bit_orYesAggregate functions
bit_xorYesAggregate functions
bool_andYesAggregate functions
bool_orYesAggregate functions
count(*)YesAggregate functions
count("any")YesAggregate functions
json(b)_aggPartialjson_agg and json_agg_strict, not jsonb_agg: JSON aggregate functions
json(b)_objectaggNo
json(b)_object_aggPartialjson_object_agg and its _strict, _unique and _unique_strict forms, not jsonb_object_agg: JSON aggregate functions
json_arrayaggNo
maxYesAggregate functions
minYesAggregate functions
range(_intersect)_aggYes
string_aggYes
sumYesAggregate functions
xmlaggYes
Statistical​
FeatureSupport StateDetails
corrYes
covar_popYes
covar_sampYes
regr_avgxYes
regr_avgyYes
regr_countYes
regr_interceptYes
regr_r2Yes
regr_slopeYes
regr_sxxYes
regr_sxyYes
regr_syyYes
stddevYesAggregate functions
stddev_popYesAggregate functions
stddev_sampYesAggregate functions
varianceYesAggregate functions
var_popYesAggregate functions
var_sampYesAggregate functions
Ordered-Set​
FeatureSupport StateDetails
modeYes
percentile_contYesThe fraction must be a constant
percentile_discYesThe fraction must be a constant

Window​

FeatureSupport StateDetails
row_numberYes
rankYes
dense_rankYes
percent_rankYes
cume_distYes
ntileYes
lagYes
leadYes
first_valueYes
last_valueYes
nth_valueYes

Subquery​

FeatureSupport StateDetails
EXISTSYes
INYes
NOT INYes
ANY/SOMEYes
ALLYes

Array Comparison​

FeatureSupport StateDetails
EXISTSYes
INYes
NOT INYes
ANY/SOMEYes
ALLYes

Set Returning​

FeatureSupport StateDetails
generate_seriesYes
generate_subscriptYes

System Information​

FeatureSupport StateDetails
pg_get_constraintdefYesCHECK expressions are printed in SereneDB's form (for example != and no casts)
pg_get_serial_sequenceYes
pg_get_indexdefYesThe access method is secondary or inverted. An inverted index shows its column dictionaries and options, INCLUDE columns and settings; ordering (DESC, NULLS FIRST) is not stored, so it is not shown
pg_get_viewdefYesThe view's query in SereneDB's one-line form
pg_get_ruledefYesThe _RETURN rule of a view, around the same query text
pg_get_triggerdefYesA trigger runs a statement, which takes the place of EXECUTE FUNCTION
pg_get_functiondefYesThe body is printed in SereneDB's form; a DuckDB macro is printed as CREATE MACRO
pg_get_function_argumentsYes
pg_get_function_identity_argumentsYes
pg_get_function_resultYes
pg_get_function_arg_defaultYes
pg_get_function_sqlbodyPartialAlways NULL: the body of a SQL function is kept as source text, shown in prosrc and by pg_get_functiondef
pg_get_partkeydefYesAlways NULL, as is pg_get_partition_constraintdef: there are no partitioned tables
pg_get_statisticsobjdefYesAlways NULL, as are pg_get_statisticsobjdef_columns and pg_get_statisticsobjdef_expressions: there are no extended statistics
pg_get_serial_sequenceYesFinds the sequence named by the column's nextval(...) default
pg_get_replica_identity_indexYesThe index of the table's primary key; NULL for a table without one
format_typeYesPrints type modifiers, for example numeric(10,2), character varying(50) and timestamp(3) without time zone
to_regclassYesAlso to_regtype, to_regtypemod, to_regproc, to_regprocedure, to_regnamespace, to_regrole, to_regcollation, to_regoper and to_regoperator; they return NULL where the cast would fail
pg_input_is_validYesAlso pg_input_error_info. For bytea, cidr, pg_lsn and the reg* types it reports PostgreSQL's message, detail, hint and SQLSTATE; for other types the message of the failed cast, with SQLSTATE 22P02
pg_get_userbyidYes
getpgusernameYes
pg_basetypeYes
pg_collation_forYesAn untyped string literal is text, so it has the default collation
system_userYesAlso the SYSTEM_USER keyword. The authentication method and the role, for example scram-sha-256:alice; NULL for a connection a trust rule admits and for the HTTP API
pg_get_keywordsYesLists the keywords of SereneDB's parser, among them pivot and qualify, which PostgreSQL does not reserve. A word the parser takes both as a column name and as a type or function name, such as struct, is listed once, as unreserved. barelabel and baredesc are NULL: the parser does not say which keywords can be a bare column label
pg_table_is_visibleYesAlso pg_type_is_visible and pg_function_is_visible. Functions are compared by name, not by argument types
obj_descriptionYesAlso col_description. Built-in types, functions and schemas have no description, so they return NULL
shobj_descriptionPartialAlways NULL: comments on databases and roles are not stored
pg_describe_objectPartialAlso pg_identify_object. Tables, views, indexes, sequences, composite types and their columns, functions, types, schemas, roles, constraints, column defaults, view rules, triggers, databases, collations, languages, access methods and tablespaces; other object classes return NULL
pg_get_aclPartialTables, views, sequences and their columns, functions, types and schemas; other object classes return NULL
pg_relation_is_updatableYesAlso pg_column_is_updatable. Tables are updatable, views are not
pg_sequence_last_valueYes
pg_sequence_parametersYesAlso pg_get_sequence_data. data_type is always bigint
pg_options_to_tableYes
pg_encoding_to_charYesAlso pg_char_to_encoding
pg_tablespace_locationYesAlways an empty string
pg_tablespace_sizeYespg_default holds every database and pg_global the catalog that all databases share
pg_tablespace_databasesYespg_default lists every database
pg_size_bytesYes
pg_column_sizePartialCounts a value as it is held in memory: a value read from a table counts as a computed one, so a short string takes 4 bytes more than its length. A whole row, as in pg_column_size(t), counts as in PostgreSQL
pg_column_compressionYesAlways NULL, as is pg_column_toast_chunk_id: values are not compressed or stored out of line one by one
pg_my_temp_schemaPartialAlways 0
acldefaultYesAlso aclexplode
makeaclitemYes
has_table_privilegeYesAlso has_column_privilege, has_any_column_privilege, has_sequence_privilege, has_schema_privilege, has_database_privilege, has_function_privilege, has_type_privilege and pg_has_role, with the role and the object given by name or by oid
has_server_privilegeYesAlso has_foreign_data_wrapper_privilege (clickhouse_fdw, iceberg_fdw and postgres_fdw) and has_tablespace_privilege (pg_default and pg_global)
has_parameter_privilegeYestrue for superusers and false for other roles: privileges on settings cannot be granted
has_largeobject_privilegeYesAlways NULL: there are no large objects
pg_settings_get_flagsPartialAn empty array for every setting, NULL for an unknown name
unicode_versionYesAlso icu_unicode_version and unicode_assigned; both versions are 17.0
pg_current_logfileYesAlways NULL: there is no logging collector
pg_jit_availableYesAlways false, as is pg_numa_available
pg_collation_actual_versionYesAlso pg_database_collation_actual_version; always NULL: collations are not versioned
pg_safe_snapshot_blocking_pidsYesAlso pg_blocking_pids; always an empty array
pg_available_wal_summariesYesReturns no rows
pg_extension_update_pathsYesFails for every extension name: no extension is available
pg_wal_lsn_diffYesAlso pg_lsn(numeric)
pg_restore_relation_statsPartialAlso pg_restore_attribute_stats; accept the arguments pg_dump --statistics writes and return false: imported statistics are not kept
pg_clear_relation_statsYesAlso pg_clear_attribute_stats; there are no imported statistics to clear
pg_stat_get_*Partialpg_stat_get_live_tuples returns the table's row count and pg_stat_get_db_numbackends the database's sessions. pg_stat_get_db_xact_commit, pg_stat_get_db_xact_rollback, pg_stat_get_db_sessions and pg_stat_get_db_session_time count from server start. Other counters and durations, function statistics included, are 0, and timestamps NULL

Behavioral Differences from PostgreSQL​

Even where a feature is supported, SereneDB intentionally diverges from PostgreSQL in a few places. SereneDB's SQL dialect closely follows PostgreSQL conventions; the exceptions are listed below.

Floating-Point Arithmetic​

A division by zero raises a Division by zero error with SQLSTATE 22012, as in PostgreSQL, and operations involving infinity values follow the IEEE Standard for Floating-Point Arithmetic (IEEE 754) on both engines. One case differs: 'NaN'::float8 / 0 raises the error too, where PostgreSQL returns NaN. SET ieee_floating_point_ops = true makes a division by zero return Infinity, NaN or -Infinity instead. In SereneDB:

SQL
SELECT 1.0 / 0.0 AS x;
SELECT 'Infinity'::FLOAT / 'Infinity'::FLOAT AS x;
SELECT 1.0 / 'Infinity'::FLOAT AS x;
SELECT 'Infinity'::FLOAT - 'Infinity'::FLOAT AS x;
SELECT 'Infinity'::FLOAT - 1.0 AS x;
db error: ERROR: Division by zero in expression (1 // 0). Use TRY(...) to return NULL for this expression, or SET error_on_division_by_zero=false to return NULL for all divisions by zero.

 x
-----
 NaN

 x
---
 0

 x
-----
 NaN

 x
----------
 Infinity

On an attached PostgreSQL:

SQL
-- PostgreSQL raises an error on division by zero (SereneDB returns Infinity per IEEE 754)SELECT * FROM postgres_query('pg', 'SELECT 1.0 / 0.0');
db error: ERROR: Failed to prepare COPY "COPY (SELECT "?column?" FROM (SELECT 1.0 / 0.0) AS __unnamed_subquery ) TO STDOUT (FORMAT "binary");": ERROR:  division by zero

Mathematical functions raise PostgreSQL's errors for an argument outside their domain, such as ln(0) or sqrt(-1). A result too large for double precision, such as exp(1000), is Infinity, where PostgreSQL reports an overflow. With ieee_floating_point_ops on, an argument outside the domain gives NaN or ±Infinity too:

SQL
SELECT ln(0) AS ln_zero;
SELECT sqrt(-1) AS sqrt_negative;
SELECT exp(1000) AS exp_overflow;
db error: ERROR: cannot take logarithm of zero

db error: ERROR: cannot take square root of a negative number

 exp_overflow
--------------
 Infinity

Integer Division Operator (//)​

Integer division itself behaves exactly like PostgreSQL: when both operands are integers, / performs integer division, so both engines return 0 here:

SQL
SELECT 1 / 2 AS x;
 x
---
 0

The divergence is that SereneDB additionally accepts the // operator as an explicit integer-division spelling, which PostgreSQL does not provide (on PostgreSQL // raises operator does not exist):

SQL
SELECT 1 // 2 AS x;
 x
---
 0

This returns 0.

UNION of Boolean and Integer Values​

The following query fails in PostgreSQL but successfully completes in SereneDB:

SQL
SELECT true AS xUNIONSELECT 2;
 x
---
 1
 2

On an attached PostgreSQL, the same query raises an error:

SQL
-- PostgreSQL rejects a UNION of boolean and integerSELECT * FROM postgres_query('pg', 'SELECT true UNION SELECT 1');
db error: ERROR: Failed to prepare query "SELECT true UNION SELECT 1": ERROR:  UNION types boolean and integer cannot be matched
LINE 1: SELECT true UNION SELECT 1
                                 ^

SereneDB instead performs an enforced cast and completes the query, returning the rows shown above.

Implicit Casting on Equality Checks​

SereneDB performs implicit casting on equality checks, e.g., converting strings to numeric and boolean values. Therefore, there are several instances where PostgreSQL throws an error while SereneDB successfully computes the result. Each expression below is evaluated on both engines -- the PostgreSQL column runs it on an attached PostgreSQL via postgres_query, the SereneDB column evaluates it locally:

SQL
ATTACH 'host=localhost port=5432 dbname=mydb user=postgres' AS pg (TYPE postgres);
SELECT '''1.1'' = 1'     AS "Expression", 'error'                                                              AS "PostgreSQL", COALESCE(TRY(('1.1' = 1)::text),     'error') AS "SereneDB"UNION ALL SELECT '''1.1'' = 1.1',  (SELECT r FROM postgres_query('pg', 'SELECT (''1.1'' = 1.1)::text r')),  COALESCE(TRY(('1.1' = 1.1)::text),  'error')UNION ALL SELECT '1 = 1.1',        (SELECT r FROM postgres_query('pg', 'SELECT (1 = 1.1)::text r')),         COALESCE(TRY((1 = 1.1)::text),      'error')UNION ALL SELECT 'true = ''true''',(SELECT r FROM postgres_query('pg', 'SELECT (true = ''true'')::text r')), COALESCE(TRY((true = 'true')::text),'error')UNION ALL SELECT 'true = 1',       'error',                                                                  COALESCE(TRY((true = 1)::text),     'error')UNION ALL SELECT '''true'' = 1',   'error',                                                                  COALESCE(TRY(('true' = 1)::text),   'error');
 Expression    | PostgreSQL | SereneDB
---------------+------------+----------
 '1.1' = 1     | error      | true
 '1.1' = 1.1   | true       | true
 1 = 1.1       | false      | false
 true = 'true' | true       | true
 true = 1      | error      | true
 'true' = 1    | error      | error

Case Sensitivity for Quoted Identifiers​

PostgreSQL lowercases unquoted identifiers and keeps the case of quoted ones. A SereneDB server does the same, so the following command creates a table named mytable, and the query for "MyTaBLe" fails because quotes keep the case:

SQL
CREATE TABLE MyTaBLe (x INTEGER);
SELECT * FROM "MyTaBLe";
db error: ERROR: Table with name MyTaBLe does not exist!
Did you mean "mytable"?

The rule works both ways: a table created with a quoted name is not found under an unquoted one.

SQL
CREATE TABLE "PreservedCase" (x INTEGER);
SELECT * FROM PreservedCase;
db error: ERROR: Table with name preservedcase does not exist!
Did you mean "PreservedCase"?

Case insensitivity therefore only works if you never quote identifiers with different cases. Column names follow the same rule; aliases inside one query, such as the output columns of a subquery, match regardless of case.

serened shell keeps DuckDB's behavior instead: identifiers are case-insensitive everywhere, even when quoted, and keep the case they were created with. A server session gets the same with the preserve_identifier_case option, under which the scripts above complete successfully:

SQL
SET preserve_identifier_case = true;
CREATE TABLE MyOtheRTaBLe (x INTEGER);
SELECT * FROM "MyOtheRTaBLe";
CREATE TABLE "PreservedCase" (x INTEGER);
SELECT * FROM PreservedCase;
SELECT tablename FROM pg_tables;
x

x

 tablename
---------------
 MyOtheRTaBLe
 PreservedCase

A server runs with preserve_identifier_case = false, which stores unquoted names in lowercase:

SQL
SET preserve_identifier_case = false;
CREATE TABLE MyTaBLe (x INTEGER);
SELECT tbl FROM sdb_tables();
 tbl
---------
 mytable

Using Double Equality Sign for Comparison​

SereneDB supports both = and == for equality comparison, while PostgreSQL only supports =.

SQL
SELECT 1 == 1 AS t;
 t
---
 t

SereneDB returns true, while on an attached PostgreSQL the same query errors (== is not a PostgreSQL operator):

SQL
-- PostgreSQL has no == operatorSELECT * FROM postgres_query('pg', 'SELECT 1 == 1');
db error: ERROR: Failed to prepare query "SELECT 1 == 1": ERROR:  operator does not exist: integer == integer
LINE 1: SELECT 1 == 1
                 ^
HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.

Note that the use of == is not encouraged due to its limited portability.

Vacuuming Tables​

In PostgreSQL, the VACUUM statement garbage collects tables and analyzes tables. In SereneDB, the VACUUM statement is only used to rebuild statistics. To reclaim space, use the CHECKPOINT statement or compact the database by creating a fresh copy with the COPY FROM DATABASE statement.

Strings​

SereneDB escapes characters such as ' when it serializes strings inside nested data structures -- for example, casting a list that contains a quote to text:

SQL
SELECT (['a''b'])::VARCHAR AS escaped;
 escaped
-----------
 ['a\\'b']

PostgreSQL's array-to-text conversion leaves the quote unescaped instead:

SQL
-- PostgreSQL's array-to-text leaves the quote unescaped (compare with SereneDB's escaped list cast)SELECT r AS "PostgreSQL" FROM postgres_query('pg', 'SELECT (ARRAY[''a''''b''])::text r');
 PostgreSQL
------------
 {a'b}

Functions​

regexp_extract Function​

Unlike PostgreSQL's regexp_substr function, SereneDB's regexp_extract returns empty strings instead of NULLs when there is no match:

SQL
SELECT regexp_extract('abc', 'x') AS result, regexp_extract('abc', 'x') IS NULL AS is_null;
 result  | is_null
---------+---------
 (empty) | f

date_part Function​

Most parts extracted by the date_part function are returned as integers. Since there are no infinite integer values in SereneDB, NULLs are returned for infinite timestamps:

SQL
SELECT date_part('year', TIMESTAMP '2021-08-03 11:59:44.123456') AS finite,       date_part('year', 'infinity'::TIMESTAMP) AS infinite;
 finite | infinite
--------+----------
   2021 | NULL

Resolution of Type Names in the Schema​

For CREATE TABLE statements, SereneDB attempts to resolve type names in the schema where a table is created. For example:

SQL
CREATE SCHEMA myschema;
CREATE TYPE myschema.mytype AS ENUM ('as', 'df');
CREATE TABLE myschema.mytable (v mytype);

On an attached PostgreSQL, the same CREATE TABLE errors because PostgreSQL does not resolve the unqualified type name in the table's schema:

SQL
-- PostgreSQL does not resolve the unqualified type name (SereneDB resolves it in the table's schema)CALL postgres_execute('pg', 'CREATE TABLE mytable (v mytype)');
db error: ERROR: Failed to execute query "(varies)CREATE TABLE mytable (v mytype)": ERROR:  type "mytype" does not exist
LINE (varies): CREATE TABLE mytable (v mytype)
                                ^

SereneDB runs the statement and creates the table successfully, confirmed by the following query:

SQL
DESCRIBE myschema.mytable;
 column_name | column_type | null | key  | default | extra
-------------+-------------+------+------+---------+-------
 v           | mytype      | YES  | NULL | NULL    | NULL

Exploiting Functional Dependencies for GROUP BY​

PostgreSQL can exploit functional dependencies, such as i -> j in the following query, and runs it. SereneDB instead raises the error shown below:

SQL
CREATE TABLE tbl (i INTEGER, j INTEGER, PRIMARY KEY (i));
SELECT jFROM tblGROUP BY i;
db error: ERROR: column "j" must appear in the GROUP BY clause or must be part of an aggregate function.
Either add it to the GROUP BY list, or use ANY_VALUE("j") if the exact value of "j" is not important.

To work around this, add the other attributes or use the GROUP BY ALL clause.