|
@@ -0,0 +1,675 @@
|
|
|
|
|
+using Postgres;
|
|
|
|
|
+
|
|
|
|
|
+namespace InvercargillSql {
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Element wrapping an int64 that can convert to int, int64, bool, or DateTime.
|
|
|
|
|
+ *
|
|
|
|
|
+ * PostgreSQL stores all integers as int64, but we want to allow seamless conversion
|
|
|
|
|
+ * to int32, bool (0/1), and DateTime (Unix epoch).
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class PostgresIntegerElement : Object, Invercargill.Element {
|
|
|
|
|
+ private int64 _value;
|
|
|
|
|
+ private bool _is_null;
|
|
|
|
|
+
|
|
|
|
|
+ public PostgresIntegerElement(int64 value, bool is_null = false) {
|
|
|
|
|
+ _value = value;
|
|
|
|
|
+ _is_null = is_null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool assignable_to_type(GLib.Type type) {
|
|
|
|
|
+ return type == typeof(int64) || type == typeof(int64?) ||
|
|
|
|
|
+ type == typeof(uint64) || type == typeof(uint64?) ||
|
|
|
|
|
+ type == typeof(int) || type == typeof(int?) ||
|
|
|
|
|
+ type == typeof(bool) || type == typeof(bool?) ||
|
|
|
|
|
+ type == typeof(DateTime) || type == typeof(DateTime?) ||
|
|
|
|
|
+ type == typeof(double) || type == typeof(double?) ||
|
|
|
|
|
+ type == typeof(float) || type == typeof(float?) ||
|
|
|
|
|
+ type == typeof(string) ||
|
|
|
|
|
+ type == typeof(long) || type == typeof(long?);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GLib.Type? type() {
|
|
|
|
|
+ return typeof(int64?);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool is_null() {
|
|
|
|
|
+ return _is_null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool try_get_as<T>(out T result) {
|
|
|
|
|
+ var requested_type = typeof(T);
|
|
|
|
|
+
|
|
|
|
|
+ if (_is_null) {
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Direct int64
|
|
|
|
|
+ if (requested_type == typeof(int64)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<int64?, T>(_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to uint64
|
|
|
|
|
+ if (requested_type == typeof(uint64)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<uint64?, T>((uint64)_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to int32
|
|
|
|
|
+ if (requested_type == typeof(int)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<int?, T>((int)_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to long
|
|
|
|
|
+ if (requested_type == typeof(long)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<long?, T>((long)_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to bool (0 = false, non-zero = true)
|
|
|
|
|
+ if (requested_type == typeof(bool)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<bool?, T>(_value != 0, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to DateTime (Unix epoch)
|
|
|
|
|
+ if (requested_type == typeof(DateTime)) {
|
|
|
|
|
+ var dt = new DateTime.from_unix_local(_value);
|
|
|
|
|
+ result = (T)dt;
|
|
|
|
|
+ return dt != null;
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to double
|
|
|
|
|
+ if (requested_type == typeof(double)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<double?, T>((double)_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to float
|
|
|
|
|
+ if (requested_type == typeof(float)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<float?, T>((float)_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to string
|
|
|
|
|
+ if (requested_type == typeof(string)) {
|
|
|
|
|
+ result = (T)_value.to_string();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public string to_string() {
|
|
|
|
|
+ return _is_null ? "PostgresIntegerElement[null]" : @"PostgresIntegerElement[$_value]";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public int64 value { get { return _value; } }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Element wrapping a double that can convert to double, float, or int64.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class PostgresRealElement : Object, Invercargill.Element {
|
|
|
|
|
+ private double _value;
|
|
|
|
|
+ private bool _is_null;
|
|
|
|
|
+
|
|
|
|
|
+ public PostgresRealElement(double value, bool is_null = false) {
|
|
|
|
|
+ _value = value;
|
|
|
|
|
+ _is_null = is_null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool assignable_to_type(GLib.Type type) {
|
|
|
|
|
+ return type == typeof(double) || type == typeof(double?) ||
|
|
|
|
|
+ type == typeof(float) || type == typeof(float?) ||
|
|
|
|
|
+ type == typeof(int64) || type == typeof(int64?) ||
|
|
|
|
|
+ type == typeof(int) || type == typeof(int?) ||
|
|
|
|
|
+ type == typeof(long) || type == typeof(long?) ||
|
|
|
|
|
+ type == typeof(string);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GLib.Type? type() {
|
|
|
|
|
+ return typeof(double?);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool is_null() {
|
|
|
|
|
+ return _is_null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool try_get_as<T>(out T result) {
|
|
|
|
|
+ var requested_type = typeof(T);
|
|
|
|
|
+
|
|
|
|
|
+ if (_is_null) {
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Direct double
|
|
|
|
|
+ if (requested_type == typeof(double)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<double?, T>(_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to float
|
|
|
|
|
+ if (requested_type == typeof(float)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<float?, T>((float)_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to int64
|
|
|
|
|
+ if (requested_type == typeof(int64)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<int64?, T>((int64)_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to int
|
|
|
|
|
+ if (requested_type == typeof(int)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<int?, T>((int)_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to long
|
|
|
|
|
+ if (requested_type == typeof(long)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<long?, T>((long)_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Convert to string
|
|
|
|
|
+ if (requested_type == typeof(string)) {
|
|
|
|
|
+ result = (T)_value.to_string();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public string to_string() {
|
|
|
|
|
+ return _is_null ? "PostgresRealElement[null]" : @"PostgresRealElement[$_value]";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public double value { get { return _value; } }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Element wrapping a string that can convert to string or parsed numeric types.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class PostgresTextElement : Object, Invercargill.Element {
|
|
|
|
|
+ private string _value;
|
|
|
|
|
+ private bool _is_null;
|
|
|
|
|
+
|
|
|
|
|
+ public PostgresTextElement(string? value) {
|
|
|
|
|
+ _value = value ?? "";
|
|
|
|
|
+ _is_null = value == null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool assignable_to_type(GLib.Type type) {
|
|
|
|
|
+ return type == typeof(string) ||
|
|
|
|
|
+ type == typeof(int64) || type == typeof(int64?) ||
|
|
|
|
|
+ type == typeof(int) || type == typeof(int?) ||
|
|
|
|
|
+ type == typeof(long) || type == typeof(long?) ||
|
|
|
|
|
+ type == typeof(double) || type == typeof(double?) ||
|
|
|
|
|
+ type == typeof(float) || type == typeof(float?) ||
|
|
|
|
|
+ type == typeof(bool) || type == typeof(bool?);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GLib.Type? type() {
|
|
|
|
|
+ return typeof(string);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool is_null() {
|
|
|
|
|
+ return _is_null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool try_get_as<T>(out T result) {
|
|
|
|
|
+ var requested_type = typeof(T);
|
|
|
|
|
+
|
|
|
|
|
+ if (_is_null && requested_type == typeof(string)) {
|
|
|
|
|
+ result = (T)"";
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (_is_null) {
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // String
|
|
|
|
|
+ if (requested_type == typeof(string)) {
|
|
|
|
|
+ result = (T)_value;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try to parse as int64
|
|
|
|
|
+ if (requested_type == typeof(int64)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<int64?, T>(int64.parse(_value), out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try to parse as int
|
|
|
|
|
+ if (requested_type == typeof(int)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<int?, T>(int.parse(_value), out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try to parse as long
|
|
|
|
|
+ if (requested_type == typeof(long)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<long?, T>(long.parse(_value), out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try to parse as double
|
|
|
|
|
+ if (requested_type == typeof(double)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<double?, T>(double.parse(_value), out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try to parse as float
|
|
|
|
|
+ if (requested_type == typeof(float)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<float?, T>((float)double.parse(_value), out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try to parse as bool
|
|
|
|
|
+ if (requested_type == typeof(bool)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<bool?, T>(_value == "t" || _value == "1" || _value.down() == "true", out result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public string to_string() {
|
|
|
|
|
+ if (_is_null) {
|
|
|
|
|
+ return "PostgresTextElement[null]";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "PostgresTextElement[%s]".printf(_value);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public string value { get { return _value; } }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Element wrapping binary data (BLOB data) using Invercargill.BinaryData.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class PostgresBlobElement : Object, Invercargill.Element {
|
|
|
|
|
+ private Invercargill.BinaryData? _value;
|
|
|
|
|
+
|
|
|
|
|
+ public PostgresBlobElement(Invercargill.BinaryData? value) {
|
|
|
|
|
+ _value = value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool assignable_to_type(GLib.Type type) {
|
|
|
|
|
+ return type == typeof(Invercargill.BinaryData);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GLib.Type? type() {
|
|
|
|
|
+ return typeof(Invercargill.BinaryData);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool is_null() {
|
|
|
|
|
+ return _value == null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool try_get_as<T>(out T result) {
|
|
|
|
|
+ var requested_type = typeof(T);
|
|
|
|
|
+
|
|
|
|
|
+ if (requested_type == typeof(Invercargill.BinaryData)) {
|
|
|
|
|
+ result = (T)_value;
|
|
|
|
|
+ return _value != null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public string to_string() {
|
|
|
|
|
+ if (_value == null) {
|
|
|
|
|
+ return "PostgresBlobElement[null]";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "PostgresBlobElement[%u bytes]".printf(_value.count());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Invercargill.BinaryData? value { get { return _value; } }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Null element representing PostgreSQL NULL values.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class PostgresNullElement : Object, Invercargill.Element {
|
|
|
|
|
+ public bool assignable_to_type(GLib.Type type) {
|
|
|
|
|
+ return true; // Can "convert" null to any nullable type
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GLib.Type? type() {
|
|
|
|
|
+ return Type.NONE;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool is_null() {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool try_get_as<T>(out T result) {
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public string to_string() {
|
|
|
|
|
+ return "PostgresNullElement[null]";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Element wrapping a PostgreSQL boolean ("t"/"f") that converts to
|
|
|
|
|
+ * bool, int, or string.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class PostgresBoolElement : Object, Invercargill.Element {
|
|
|
|
|
+ private bool _value;
|
|
|
|
|
+ private bool _is_null;
|
|
|
|
|
+
|
|
|
|
|
+ public PostgresBoolElement(bool value, bool is_null = false) {
|
|
|
|
|
+ _value = value;
|
|
|
|
|
+ _is_null = is_null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool assignable_to_type(GLib.Type type) {
|
|
|
|
|
+ return type == typeof(bool) || type == typeof(bool?) ||
|
|
|
|
|
+ type == typeof(int) || type == typeof(int?) ||
|
|
|
|
|
+ type == typeof(int64) || type == typeof(int64?) ||
|
|
|
|
|
+ type == typeof(string);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GLib.Type? type() {
|
|
|
|
|
+ return typeof(bool?);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool is_null() {
|
|
|
|
|
+ return _is_null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool try_get_as<T>(out T result) {
|
|
|
|
|
+ var requested_type = typeof(T);
|
|
|
|
|
+
|
|
|
|
|
+ if (_is_null) {
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (requested_type == typeof(bool)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<bool?, T>(_value, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (requested_type == typeof(int) || requested_type == typeof(int64)) {
|
|
|
|
|
+ int64 as_int = _value ? 1 : 0;
|
|
|
|
|
+ if (requested_type == typeof(int)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<int?, T>((int)as_int, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<int64?, T>(as_int, out result);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (requested_type == typeof(string)) {
|
|
|
|
|
+ result = (T)(_value ? "t" : "f");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public string to_string() {
|
|
|
|
|
+ return _is_null ? "PostgresBoolElement[null]" : @"PostgresBoolElement[$_value]";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool value { get { return _value; } }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Element wrapping a timestamp value that converts to DateTime,
|
|
|
|
|
+ * string, or Unix epoch int64.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class PostgresDateTimeElement : Object, Invercargill.Element {
|
|
|
|
|
+ private DateTime _value;
|
|
|
|
|
+ private bool _is_null;
|
|
|
|
|
+
|
|
|
|
|
+ public PostgresDateTimeElement(DateTime? value) {
|
|
|
|
|
+ _value = value ?? new DateTime.from_unix_local(0);
|
|
|
|
|
+ _is_null = value == null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool assignable_to_type(GLib.Type type) {
|
|
|
|
|
+ return type == typeof(DateTime) || type == typeof(DateTime?) ||
|
|
|
|
|
+ type == typeof(string) ||
|
|
|
|
|
+ type == typeof(int64) || type == typeof(int64?);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public GLib.Type? type() {
|
|
|
|
|
+ return typeof(DateTime?);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool is_null() {
|
|
|
|
|
+ return _is_null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public bool try_get_as<T>(out T result) {
|
|
|
|
|
+ var requested_type = typeof(T);
|
|
|
|
|
+
|
|
|
|
|
+ if (_is_null) {
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (requested_type == typeof(DateTime)) {
|
|
|
|
|
+ result = (T)_value;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (requested_type == typeof(string)) {
|
|
|
|
|
+ result = (T)_value.format_iso8601();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (requested_type == typeof(int64)) {
|
|
|
|
|
+ return Invercargill.Nullable.convert_nullability<int64?, T>(_value.to_unix(), out result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public string to_string() {
|
|
|
|
|
+ return _is_null ? "PostgresDateTimeElement[null]" : @"PostgresDateTimeElement[$_value]";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public DateTime value { get { return _value; } }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Factory to create Elements from PostgreSQL result values and to
|
|
|
|
|
+ * convert Element parameter values into libpq text-format strings.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class PostgresElementFactory {
|
|
|
|
|
+ // Well-known type OIDs for mapping result columns to element types.
|
|
|
|
|
+ internal const uint OID_BOOL = 16;
|
|
|
|
|
+ internal const uint OID_BYTEA = 17;
|
|
|
|
|
+ internal const uint OID_INT8 = 20;
|
|
|
|
|
+ internal const uint OID_INT2 = 21;
|
|
|
|
|
+ internal const uint OID_INT4 = 23;
|
|
|
|
|
+ internal const uint OID_TEXT = 25;
|
|
|
|
|
+ internal const uint OID_OID = 26;
|
|
|
|
|
+ internal const uint OID_FLOAT4 = 700;
|
|
|
|
|
+ internal const uint OID_FLOAT8 = 701;
|
|
|
|
|
+ internal const uint OID_VARCHAR = 1043;
|
|
|
|
|
+ internal const uint OID_DATE = 1082;
|
|
|
|
|
+ internal const uint OID_TIMESTAMP = 1114;
|
|
|
|
|
+ internal const uint OID_TIMESTAMPTZ = 1184;
|
|
|
|
|
+ internal const uint OID_NUMERIC = 1700;
|
|
|
|
|
+
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Creates an appropriate Element from a PGresult field value.
|
|
|
|
|
+ * Unknown OIDs fall back to text, which round-trips correctly for
|
|
|
|
|
+ * text, varchar, numeric, uuid, json and friends.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static Invercargill.Element from_result(Postgres.Result result, int row, int col, uint type_oid) {
|
|
|
|
|
+ if (result.is_null(row, col)) {
|
|
|
|
|
+ return new PostgresNullElement();
|
|
|
|
|
+ }
|
|
|
|
|
+ unowned string value = result.get_value(row, col);
|
|
|
|
|
+ switch (type_oid) {
|
|
|
|
|
+ case OID_BOOL:
|
|
|
|
|
+ return new PostgresBoolElement(value == "t" || value == "true" || value == "1");
|
|
|
|
|
+ case OID_BYTEA:
|
|
|
|
|
+ return new PostgresBlobElement(decode_bytea(value));
|
|
|
|
|
+ case OID_INT2:
|
|
|
|
|
+ case OID_INT4:
|
|
|
|
|
+ case OID_INT8:
|
|
|
|
|
+ case OID_OID:
|
|
|
|
|
+ return new PostgresIntegerElement(int64.parse(value));
|
|
|
|
|
+ case OID_FLOAT4:
|
|
|
|
|
+ case OID_FLOAT8:
|
|
|
|
|
+ return new PostgresRealElement(double.parse(value));
|
|
|
|
|
+ case OID_TIMESTAMP:
|
|
|
|
|
+ case OID_TIMESTAMPTZ:
|
|
|
|
|
+ case OID_DATE:
|
|
|
|
|
+ return new PostgresDateTimeElement(parse_timestamp(value));
|
|
|
|
|
+ default:
|
|
|
|
|
+ return new PostgresTextElement(value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Decodes a text-format bytea value: hex form "\x48656c6c6f"
|
|
|
|
|
+ * (server 9+) or legacy octal-escape form. Returns null for empty
|
|
|
|
|
+ * bytea, matching SqliteBlobElement(null) semantics.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static Invercargill.BinaryData? decode_bytea(string value) {
|
|
|
|
|
+ if (value.has_prefix("\\x")) {
|
|
|
|
|
+ string hex = value.substring(2);
|
|
|
|
|
+ if (hex.length == 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ var bytes = new uint8[hex.length / 2];
|
|
|
|
|
+ for (int i = 0; i < bytes.length; i++) {
|
|
|
|
|
+ bytes[i] = (uint8)((hex_value(hex[i * 2]) << 4) | hex_value(hex[i * 2 + 1]));
|
|
|
|
|
+ }
|
|
|
|
|
+ return new Invercargill.DataStructures.ByteBuffer.from_byte_array(bytes);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Legacy octal escapes: backslash-escaped bytes or \NNN octal.
|
|
|
|
|
+ var builder = new uint8[value.length];
|
|
|
|
|
+ int out_len = 0;
|
|
|
|
|
+ for (int i = 0; i < value.length; i++) {
|
|
|
|
|
+ char c = value[i];
|
|
|
|
|
+ if (c == '\\' && i + 1 < value.length) {
|
|
|
|
|
+ char n1 = value[i + 1];
|
|
|
|
|
+ if (n1 == '\\') {
|
|
|
|
|
+ builder[out_len++] = '\\';
|
|
|
|
|
+ i += 1;
|
|
|
|
|
+ } else if (n1 >= '0' && n1 <= '7' && i + 3 < value.length &&
|
|
|
|
|
+ value[i + 2] >= '0' && value[i + 2] <= '7' &&
|
|
|
|
|
+ value[i + 3] >= '0' && value[i + 3] <= '7') {
|
|
|
|
|
+ int octal = (n1 - '0') * 64 + (value[i + 2] - '0') * 8 + (value[i + 3] - '0');
|
|
|
|
|
+ builder[out_len++] = (uint8)octal;
|
|
|
|
|
+ i += 3;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ builder[out_len++] = (uint8)n1;
|
|
|
|
|
+ i += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ builder[out_len++] = (uint8)c;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (out_len == 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ var trimmed = new uint8[out_len];
|
|
|
|
|
+ GLib.Memory.copy(trimmed, builder, out_len);
|
|
|
|
|
+ return new Invercargill.DataStructures.ByteBuffer.from_byte_array(trimmed);
|
|
|
|
|
+ }
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Encodes binary data as text-format bytea hex form. PostgreSQL
|
|
|
|
|
+ * accepts "\x<hex>" for bytea parameters in text format on any
|
|
|
|
|
+ * modern server, so no cast is required at bind time.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static string encode_bytea(uint8[] data) {
|
|
|
|
|
+ var builder = new StringBuilder("\\x");
|
|
|
|
|
+ foreach (uint8 b in data) {
|
|
|
|
|
+ builder.append_printf("%02x", b);
|
|
|
|
|
+ }
|
|
|
|
|
+ return builder.str;
|
|
|
|
|
+ }
|
|
|
|
|
+ private static int hex_value(char c) {
|
|
|
|
|
+ if (c >= '0' && c <= '9') return c - '0';
|
|
|
|
|
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
|
|
|
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Parses a PostgreSQL text-format timestamp into a DateTime.
|
|
|
|
|
+ * Normalises the space separator to "T" and, when no UTC offset is
|
|
|
|
|
+ * present, assumes UTC so values stay unambiguous.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static DateTime? parse_timestamp(string value) {
|
|
|
|
|
+ int sp = value.index_of(" ");
|
|
|
|
|
+ string normalised = sp >= 0 ? value.substring(0, sp) + "T" + value.substring(sp + 1) : value;
|
|
|
|
|
+ bool has_offset = normalised.contains("+") || (
|
|
|
|
|
+ normalised.last_index_of("-") > 9 &&
|
|
|
|
|
+ normalised.last_index_of("-") < normalised.length - 1 &&
|
|
|
|
|
+ normalised[normalised.last_index_of("-") + 1].isdigit()
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!has_offset) {
|
|
|
|
|
+ normalised += "+00:00";
|
|
|
|
|
+ }
|
|
|
|
|
+ DateTime? dt = new DateTime.from_iso8601(normalised, null);
|
|
|
|
|
+ if (dt == null) {
|
|
|
|
|
+ dt = new DateTime.from_iso8601(normalised.substring(0, 19), null);
|
|
|
|
|
+ }
|
|
|
|
|
+ return dt;
|
|
|
|
|
+ }
|
|
|
|
|
+ /*
|
|
|
|
|
+ * Converts an Element parameter value into a text-format parameter
|
|
|
|
|
+ * string for PQexecParams, or null for SQL NULL.
|
|
|
|
|
+ * Mirrors SqliteElementFactory.bind_to_statement conversions:
|
|
|
|
|
+ * DateTime -> ISO 8601 text, bool -> "t"/"f", BinaryData -> bytea hex.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static string? to_param_text(Invercargill.Element element) {
|
|
|
|
|
+ if (element.is_null()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ // BinaryData first: blobs must not fall through to the string branch.
|
|
|
|
|
+ if (element.assignable_to_type(typeof(Invercargill.BinaryData))) {
|
|
|
|
|
+ Invercargill.BinaryData? blob;
|
|
|
|
|
+ if (element.try_get_as<Invercargill.BinaryData>(out blob) && blob != null) {
|
|
|
|
|
+ uint8[] blob_data = blob.to_array();
|
|
|
|
|
+ return encode_bytea(blob_data);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // DateTime -> ISO 8601 text
|
|
|
|
|
+ if (element.assignable_to_type(typeof(DateTime))) {
|
|
|
|
|
+ DateTime? dt;
|
|
|
|
|
+ if (element.try_get_as<DateTime>(out dt) && dt != null) {
|
|
|
|
|
+ return dt.format_iso8601();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // bool -> "t"/"f"
|
|
|
|
|
+ if (element.assignable_to_type(typeof(bool))) {
|
|
|
|
|
+ bool? b;
|
|
|
|
|
+ if (element.try_get_as<bool>(out b) && b != null) {
|
|
|
|
|
+ return b ? "t" : "f";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try int64
|
|
|
|
|
+ if (element.assignable_to_type(typeof(int64))) {
|
|
|
|
|
+ int64? i;
|
|
|
|
|
+ if (element.try_get_as<int64?>(out i) && i != null) {
|
|
|
|
|
+ return i.to_string();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try int
|
|
|
|
|
+ if (element.assignable_to_type(typeof(int))) {
|
|
|
|
|
+ int? i;
|
|
|
|
|
+ if (element.try_get_as<int>(out i) && i != null) {
|
|
|
|
|
+ return i.to_string();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try long
|
|
|
|
|
+ if (element.assignable_to_type(typeof(long))) {
|
|
|
|
|
+ long? l;
|
|
|
|
|
+ if (element.try_get_as<long>(out l) && l != null) {
|
|
|
|
|
+ return l.to_string();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try uint64
|
|
|
|
|
+ if (element.assignable_to_type(typeof(uint64))) {
|
|
|
|
|
+ uint64? u;
|
|
|
|
|
+ if (element.try_get_as<uint64?>(out u) && u != null) {
|
|
|
|
|
+ return u.to_string();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try double
|
|
|
|
|
+ if (element.assignable_to_type(typeof(double))) {
|
|
|
|
|
+ double? d;
|
|
|
|
|
+ if (element.try_get_as<double?>(out d) && d != null) {
|
|
|
|
|
+ return d.to_string();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try float
|
|
|
|
|
+ if (element.assignable_to_type(typeof(float))) {
|
|
|
|
|
+ float? f;
|
|
|
|
|
+ if (element.try_get_as<float?>(out f) && f != null) {
|
|
|
|
|
+ return f.to_string();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Try string
|
|
|
|
|
+ if (element.assignable_to_type(typeof(string))) {
|
|
|
|
|
+ string? s;
|
|
|
|
|
+ if (element.try_get_as<string>(out s) && s != null) {
|
|
|
|
|
+ return s;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Fallback: SQL NULL, matching the sqlite factory.
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|