1use crate::{
8 encode::StaticType,
9 interval::Interval,
10 json::Json,
11 types::{
12 BPCHAROID, BYTEAOID, CIDROID, DATEOID, INETOID, JSONBOID, JSONOID, NAMEOID, Oid, TEXTOID, TIMEOID,
13 TIMESTAMPOID, TIMESTAMPTZOID, TIMETZOID, VARCHAROID, array_oid_checked,
14 },
15};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct ColumnSpec {
20 pub name: &'static str,
22 pub type_oid: Oid,
24 pub nullable: bool,
26 pub known: bool,
29}
30
31pub trait RowShape {
33 const SHAPE: &'static [ColumnSpec];
35}
36
37const fn str_eq(a: &str, b: &str) -> bool {
38 let a = a.as_bytes();
39 let b = b.as_bytes();
40 if a.len() != b.len() {
41 return false;
42 }
43 let mut i = 0;
44 while i < a.len() {
45 if a[i] != b[i] {
46 return false;
47 }
48 i += 1;
49 }
50 true
51}
52
53pub const fn decodes_from(rust_oid: Oid, column_oid: Oid) -> bool {
59 if rust_oid == column_oid {
60 return true;
61 }
62 match rust_oid {
63 TEXTOID => matches!(column_oid, VARCHAROID | BPCHAROID | NAMEOID),
64 TIMESTAMPTZOID => matches!(column_oid, DATEOID | TIMESTAMPOID | TIMEOID | TIMETZOID),
65 JSONBOID => column_oid == JSONOID,
66 INETOID => column_oid == CIDROID,
67 _ => false,
68 }
69}
70
71pub const fn verify_shape(expected: &[ColumnSpec], actual: &[ColumnSpec]) {
76 if expected.len() != actual.len() {
77 panic!("postgresql: the query returns a different number of columns than the target type expects");
78 }
79 let mut i = 0;
80 while i < expected.len() {
81 let e = expected[i];
82 let a = actual[i];
83 if !decodes_from(e.type_oid, a.type_oid) {
84 panic!("postgresql: a result column has a type incompatible with its target field");
85 }
86 if e.nullable != a.nullable && a.known {
87 panic!(
88 "postgresql: a result column's nullability does not match its target field (wrap it in Option or make the column NOT NULL)"
89 );
90 }
91 if !e.name.is_empty() && !str_eq(e.name, a.name) {
92 panic!("postgresql: a result column name does not match its target field name");
93 }
94 i += 1;
95 }
96}
97
98macro_rules! scalar_shape {
99 ($($ty:ty),* $(,)?) => {
100 $(
101 impl RowShape for $ty {
102 const SHAPE: &'static [ColumnSpec] = &[ColumnSpec {
103 name: "",
104 type_oid: <$ty as StaticType>::OID,
105 nullable: <$ty as StaticType>::NULLABLE,
106 known: true,
107 }];
108 }
109 )*
110 };
111}
112
113scalar_shape!(
114 i16,
115 i32,
116 i64,
117 f32,
118 f64,
119 bool,
120 String,
121 uuid::Uuid,
122 time::DateTime,
123 serde_json::Value,
124 ipnetwork::IpNetwork,
125 Interval,
126);
127
128impl<T: StaticType> RowShape for Option<T> {
129 const SHAPE: &'static [ColumnSpec] = &[ColumnSpec {
130 name: "",
131 type_oid: T::OID,
132 nullable: true,
133 known: true,
134 }];
135}
136
137impl<T: StaticType> RowShape for Vec<T> {
138 const SHAPE: &'static [ColumnSpec] = &[ColumnSpec {
139 name: "",
140 type_oid: array_oid_checked(T::OID),
141 nullable: false,
142 known: true,
143 }];
144}
145
146macro_rules! tuple_shape {
147 ($($name:ident),+) => {
148 impl<$($name: StaticType),+> RowShape for ($($name,)+) {
149 const SHAPE: &'static [ColumnSpec] = &[
150 $(ColumnSpec {
151 name: "",
152 type_oid: <$name as StaticType>::OID,
153 nullable: <$name as StaticType>::NULLABLE,
154 known: true,
155 },)+
156 ];
157 }
158 };
159}
160
161tuple_shape!(A);
162tuple_shape!(A, B);
163tuple_shape!(A, B, C);
164tuple_shape!(A, B, C, D);
165tuple_shape!(A, B, C, D, E);
166tuple_shape!(A, B, C, D, E, F);
167tuple_shape!(A, B, C, D, E, F, G);
168tuple_shape!(A, B, C, D, E, F, G, H);
169
170impl<T> RowShape for Json<T> {
171 const SHAPE: &'static [ColumnSpec] = &[ColumnSpec {
172 name: "",
173 type_oid: JSONBOID,
174 nullable: false,
175 known: true,
176 }];
177}
178
179impl RowShape for Vec<u8> {
181 const SHAPE: &'static [ColumnSpec] = &[ColumnSpec {
182 name: "",
183 type_oid: BYTEAOID,
184 nullable: false,
185 known: true,
186 }];
187}