4 SQL Types and Conversions
Connections automatically convert query results to appropriate Racket types. Likewise, query parameters are accepted as Racket values and converted to the appropriate SQL type.
> (query-value pgc "select count(*) from the_numbers") 4
> (query-value pgc "select false") #f
> (query-value pgc "select 1 + $1" 2) 3
If a query result contains a column with a SQL type not supported by this library, an exception is raised. As a workaround, cast the column to a supported type:
> (query-value pgc "select inet '127.0.0.1'") query-value: unsupported type
type: inet
typeid: 869
> (query-value pgc "select cast(inet '127.0.0.1' as varchar)") "127.0.0.1/32"
The exception for unsupported types in result columns is raised when the query is executed, not when it is prepared; for parameters it is raised when the parameter values are supplied. Thus even unexecutable prepared statements can be inspected using prepared-statement-parameter-types and prepared-statement-result-types.
4.1 SQL Type Conversions
This section describes the correspondences between SQL types and Racket types for the supported database systems.
4.1.1 PostgreSQL Types
This section applies to connections created with postgresql-connect.
The following table lists the PostgreSQL types known to this library, along with their corresponding Racket representations.
PostgreSQL type
pg_type.typname
Racket type
'boolean
bool
'char1
char
'smallint
int2
'integer
int4
'bigint
int8
'real
float4
'double
float8
'decimal
numeric
rational? or +nan.0
'character
bpchar
'varchar
varchar
'text
text
'bytea
bytea
'date
date
'time
time
'timetz
timetz
'timestamp
timestamp
sql-timestamp? or -inf.0 or +inf.0
'timestamptz
timestamptz
sql-timestamp? or -inf.0 or +inf.0
'interval
interval
'bit
bit
bit-vector?
'varbit
varbit
bit-vector?
'json
json
'jsonb
jsonb
'int4range
int4range
'int8range
int8range
'numrange
numrange
'tsrange
tsrange
'tstzrange
tstzrange
'daterange
daterange
'point
point
'lseg
lseg
'path
path
'box
box
'polygon
polygon
'circle
circle
The 'char1 type, written "char" in PostgreSQL’s SQL syntax (the quotation marks are significant), is one byte, essentially a tiny integer written as a character.
A SQL value of type decimal is converted to either an exact rational or +nan.0. When converting Racket values to SQL decimal, exact rational values representable by finite decimal strings are converted without loss of precision. (Precision may be lost, of course, if the value is then stored in a database field of lower precision.) Other real values are converted to decimals with a loss of precision. In PostgreSQL, numeric and decimal refer to the same type.
> (query-value pgc "select real '+Infinity'") +inf.0
> (query-value pgc "select numeric '12345678901234567890'") 12345678901234567890
A SQL timestamp with time zone is converted to a Racket
sql-timestamp in UTC—
The geometric types such as 'point are represented by structures defined in the db/util/geometry and db/util/postgresql modules.
PostgreSQL user-defined domains are supported in query results if the underlying type is supported. Recordset headers and prepared-statement-result-types report them in terms of the underlying type. Parameters with user-defined domain types are not currently supported. As a workaround, cast the parameter to the underlying type. For example, if the type of $1 is a domain whose underlying type is integer, then replace $1 with ($1::integer).
For each type in the table above, the corresponding array type is also supported, using the pg-array structure. Use the = ANY syntax with an array parameter instead of dynamically constructing a SQL IN expression:
> (query-value pgc "select 1 in (1, 2, 3)") #t
> (query-value pgc "select 1 = any ($1::integer[])" (list->pg-array (list 1 2 3))) #t
A list may be provided for an array parameter, in which case it is automatically converted using list->pg-array. The type annotation can be dropped when the array type can be inferred from the left-hand side.
> (query-value pgc "select 1 = any ($1)" (list 1 2 3)) #t
> (query-value pgc "select $1::integer = any ($2)" 1 (list 1 2 3)) #t
> (query-value pgc "select $1 = any ($2)" ; what type are we using? 1 (list 1 2 3)) query-value: cannot convert given value to SQL type
given: 1
type: string
expected: string?
dialect: PostgreSQL
PostgreSQL defines many other types, such as network addresses and row types. These are currently not supported, but support may be added in future versions of this library.
4.1.2 MySQL Types
This section applies to connections created with mysql-connect.
The following table lists the MySQL types known to this library, along with their corresponding Racket representations.
MySQL type
Racket type
'integer
'tinyint
'smallint
'mediumint
'bigint
'real
'double
'decimal
'varchar
'date
'time
'datetime
'var-string
'text
'var-binary
'blob
'bit
bit-vector?
'geometry
MySQL does not report specific parameter types for prepared queries,
so they are instead assigned the pseudo-type 'any. Conversion
of Racket values to parameters accepts strings, numbers
(rational?—
Fields of type CHAR or VARCHAR are typically reported as 'var-string, and fields of type BINARY or VARBINARY are typically reported as 'var-binary.
The MySQL time type represents time intervals, which may not correspond to times of day (for example, the interval may be negative or larger than 24 hours). In conversion from MySQL results to Racket values, those time values that represent times of day are converted to sql-time values; the rest are represented by sql-interval values.
The MySQL enum and set types are not supported. As a workaround, cast them to/from either integers or strings.
4.1.3 SQLite Types
This section applies to connections created with sqlite3-connect.
The following table lists the SQLite types known to this library, along with their corresponding Racket representations.
Unlike PostgreSQL and MySQL, SQLite does not enforce declared type constraints (with the exception of integer primary key) on columns. Rather, every SQLite value has an associated “storage class”.
SQLite storage class
Racket type
integer
real
text
blob
SQLite does not report specific parameter and result types for prepared queries. Instead, they are assigned the pseudo-type 'any. Conversion of Racket values to parameters accepts strings, bytes, and real numbers.
An exact integer that cannot be represented as a 64-bit signed integer is converted as real, not integer.
> (expt 2 80) 1208925819614629174706176
> (query-value slc "select ?" (expt 2 80)) 1.2089258196146292e+24
4.1.4 ODBC Types
This section applies to connections created with odbc-connect or odbc-driver-connect.
The following table lists the ODBC types known to this library, along with their corresponding Racket representations.
ODBC type
Racket type
'character
'varchar
'longvarchar
'numeric
'decimal
'integer
'tinyint
'smallint
'bigint
'float
'real
'double
'date
'time
'datetime
'timestamp
'binary
'varbinary
'longvarbinary
'bit1
Not all ODBC drivers provide specific parameter type information for
prepared queries. Some omit parameter type information entirely or,
worse, assign all parameters a single type such as varchar. To
avoid enforcing irrelevant type constraints in the last case,
connections only attempt to fetch and enforce parameter types when the
connection is made using the #:strict-parameter-type?
option. Otherwise, the connection assigns all parameters the type
'unknown. (The 'unknown type is also used when
specific parameter types are requested but are not available.)
Conversion of Racket values to 'unknown parameters accepts
strings, bytes, numbers (rational?—
The ODBC type 'bit1 represents a single bit, unlike the standard SQL bit(N) type.
Interval types are not currently supported on ODBC.
4.2 SQL Data
This section describes data types for representing various SQL types that have no existing appropriate counterpart in Racket.
4.2.1 SQL NULL
SQL NULL is translated into the unique sql-null value.
> (query-value pgc "select NULL") #<sql-null>
procedure
(sql-null->false x) → any/c
x : any/c
> (sql-null->false "apple") "apple"
> (sql-null->false sql-null) #f
> (sql-null->false #f) #f
procedure
(false->sql-null x) → any/c
x : any/c
> (false->sql-null "apple") "apple"
> (false->sql-null #f) #<sql-null>
4.2.2 Dates and Times
The DATE, TIME (WITH TIME ZONE and without), TIMESTAMP (WITH TIME ZONE and without), and INTERVAL SQL types are represented by the following structures.
See also Datetime Type Utilities for more functions on datetime values.
struct
year : exact-integer? month : (integer-in 0 12) day : (integer-in 0 31)
Dates with zero-valued month or day components are a MySQL extension.
struct
hour : exact-nonnegative-integer? minute : exact-nonnegative-integer? second : exact-nonnegative-integer? nanosecond : exact-nonnegative-integer? tz : (or/c exact-integer? #f)
struct
(struct sql-timestamp ( year month day hour minute second nanosecond tz)) year : exact-nonnegative-integer? month : exact-nonnegative-integer? day : exact-nonnegative-integer? hour : exact-nonnegative-integer? minute : exact-nonnegative-integer? second : exact-nonnegative-integer? nanosecond : exact-nonnegative-integer? tz : (or/c exact-integer? #f)
The tz field indicates the time zone offset as the number of seconds east of GMT (as in srfi/19). If tz is #f, the time or timestamp does not carry time zone information.
The sql-time and sql-timestamp structures store fractional seconds to nanosecond precision for compatibility with srfi/19. Note, however, that database systems generally do not support nanosecond precision; PostgreSQL, for example, only supports microsecond precision.
> (query-value pgc "select date '25-dec-1980'") (sql-date 1980 12 25)
> (query-value pgc "select time '7:30'") (sql-time 7 30 0 0 #f)
> (query-value pgc "select timestamp 'epoch'") (sql-timestamp 1970 1 1 0 0 0 0 #f)
> (query-value pgc "select timestamp with time zone 'epoch'") (sql-timestamp 1970 1 1 0 0 0 0 0)
struct
(struct sql-interval ( years months days hours minutes seconds nanoseconds)) years : exact-integer? months : exact-integer? days : exact-integer? hours : exact-integer? minutes : exact-integer? seconds : exact-integer? nanoseconds : exact-integer?
That is, an interval consists of two groups of components: year-month and day-time, and normalization is done only within groups. In fact, the SQL standard recognizes those two types of intervals separately (see sql-year-month-interval? and sql-day-time-interval?, below), and does not permit combining them. Intervals such as 1 month 3 days are a PostgreSQL extension.
procedure
x : any/c
procedure
x : any/c
procedure
(sql-interval->sql-time interval [failure]) → any
interval : sql-interval? failure : any/c = (lambda () (error ....))
hours, minutes, seconds, and nanoseconds must all be non-negative
hours must be between 0 and 23
If interval is out of range, the failure value is called, if it is a procedure, or returned, otherwise.
procedure
time : sql-time?
4.2.3 Bits
The BIT and BIT VARYING (VARBIT) SQL types are represented by bit-vectors (data/bit-vector).
The following functions are provided for backwards compatibility. They are deprecated and will be removed in a future release of Racket.
procedure
(make-sql-bits len) → sql-bits?
len : exact-nonnegative-integer?
procedure
v : any/c
procedure
b : sql-bits?
procedure
(sql-bits-ref b i) → boolean?
b : sql-bits? i : exact-nonnegative-integer?
procedure
(sql-bits-set! b i v) → void?
b : sql-bits? i : exact-nonnegative-integer? v : boolean?
procedure
(sql-bits->list b) → (listof boolean?)
b : sql-bits?
procedure
(sql-bits->string b) → string?
b : sql-bits?
procedure
(list->sql-bits lst) → sql-bits?
lst : (listof boolean?)
procedure
(string->sql-bits s) → sql-bits?
s : string?