1+ use crate :: query:: bounds:: FieldIdentifier ;
12use crate :: query:: querybuilder:: syntax:: symbol:: Symbol :: Dot ;
23use crate :: query:: querybuilder:: syntax:: tokens:: { SqlToken , ToSqlTokens } ;
34
@@ -8,9 +9,18 @@ pub struct ColumnRef<'a> {
89 pub alias : Option < & ' a str > ,
910}
1011
12+ impl < ' a , T > From < T > for ColumnRef < ' a >
13+ where
14+ T : FieldIdentifier ,
15+ {
16+ fn from ( value : T ) -> Self {
17+ Self :: from ( value. as_str ( ) )
18+ }
19+ }
20+
1121impl < ' a > From < & ' a str > for ColumnRef < ' a > {
1222 fn from ( value : & ' a str ) -> Self {
13- Self :: new ( value)
23+ __impl :: column_ref_from_str_ref ( value)
1424 }
1525}
1626
@@ -32,7 +42,11 @@ impl<'a> ToSqlTokens<'a> for ColumnRef<'a> {
3242
3343impl < ' a > ColumnRef < ' a > {
3444 pub fn new ( column_name : & ' a str ) -> Self {
35- Self { column : column_name, table : None , alias : None }
45+ Self {
46+ column : column_name,
47+ table : None ,
48+ alias : None ,
49+ }
3650 }
3751
3852 /// mutator that allows to modify a [`ColumnRef`] to have a <table>.<column> format
@@ -52,3 +66,224 @@ impl<'a> ColumnRef<'a> {
5266 self
5367 }
5468}
69+
70+ mod __impl {
71+ use crate :: query:: querybuilder:: syntax:: column:: { __detail, ColumnRef } ;
72+
73+ pub ( crate ) fn column_ref_from_str_ref ( value : & ' _ str ) -> ColumnRef < ' _ > {
74+ let trimmed = value. trim ( ) ;
75+
76+ let ( before_alias, alias) = match __detail:: find_case_insensitive_as ( trimmed) {
77+ Some ( idx) => {
78+ let ( left, right) = trimmed. split_at ( idx) ;
79+ let right = right[ 2 ..] . trim_start ( ) ;
80+ ( left. trim ( ) , Some ( right. trim ( ) ) )
81+ }
82+ None => ( trimmed, None ) ,
83+ } ;
84+
85+ let ( table, column) = match before_alias. split_once ( '.' ) {
86+ Some ( ( tbl, col) ) => ( Some ( tbl. trim ( ) ) , col. trim ( ) ) ,
87+ None => ( None , before_alias. trim ( ) ) ,
88+ } ;
89+
90+ ColumnRef {
91+ table,
92+ column,
93+ alias,
94+ }
95+ }
96+ }
97+
98+ mod __detail {
99+ pub ( crate ) fn find_case_insensitive_as ( s : & str ) -> Option < usize > {
100+ let bytes = s. as_bytes ( ) ;
101+ for i in 0 ..bytes. len ( ) . saturating_sub ( 2 ) {
102+ let a = bytes[ i] ;
103+ let b = bytes[ i + 1 ] ;
104+
105+ // Match case-insensitive ASCII
106+ let is_a = a == b'a' || a == b'A' ;
107+ let is_s = b == b's' || b == b'S' ;
108+
109+ if is_a && is_s {
110+ let before_ok = i > 0 && bytes[ i - 1 ] . is_ascii_whitespace ( ) ;
111+ let after_ok = i + 2 < bytes. len ( ) && bytes[ i + 2 ] . is_ascii_whitespace ( ) ;
112+
113+ if before_ok && after_ok {
114+ return Some ( i) ;
115+ }
116+ }
117+ }
118+ None
119+ }
120+ }
121+
122+ #[ cfg( test) ]
123+ mod tests {
124+ use super :: ColumnRef ;
125+ use crate :: query:: querybuilder:: syntax:: column:: __detail:: find_case_insensitive_as;
126+
127+ // ------------------------------------------------------------
128+ // Tests for find_case_insensitive_as
129+ // ------------------------------------------------------------
130+ #[ test]
131+ fn test_find_as_basic_uppercase ( ) {
132+ let idx = find_case_insensitive_as ( "col AS x" ) . unwrap ( ) ;
133+ assert_eq ! ( & "col AS x" [ idx..idx + 2 ] , "AS" ) ;
134+ }
135+
136+ #[ test]
137+ fn test_find_as_lowercase ( ) {
138+ let idx = find_case_insensitive_as ( "col as x" ) . unwrap ( ) ;
139+ assert_eq ! ( & "col as x" [ idx..idx + 2 ] , "as" ) ;
140+ }
141+
142+ #[ test]
143+ fn test_find_as_mixed_case ( ) {
144+ let idx = find_case_insensitive_as ( "col As x" ) . unwrap ( ) ;
145+ assert_eq ! ( & "col As x" [ idx..idx + 2 ] , "As" ) ;
146+ }
147+
148+ #[ test]
149+ fn test_find_as_with_multiple_spaces ( ) {
150+ let idx = find_case_insensitive_as ( "col AS x" ) . unwrap ( ) ;
151+ assert_eq ! ( & "col AS x" [ idx..idx + 2 ] , "AS" ) ;
152+ }
153+
154+ #[ test]
155+ fn test_find_as_requires_space_before_and_after ( ) {
156+ assert ! ( find_case_insensitive_as( "colASx" ) . is_none( ) ) ;
157+ assert ! ( find_case_insensitive_as( "col ASx" ) . is_none( ) ) ;
158+ assert ! ( find_case_insensitive_as( "colAS x" ) . is_none( ) ) ;
159+ assert ! ( find_case_insensitive_as( "ASx" ) . is_none( ) ) ;
160+ assert ! ( find_case_insensitive_as( "xAS" ) . is_none( ) ) ;
161+ }
162+
163+ #[ test]
164+ fn test_find_as_at_start_or_end ( ) {
165+ assert ! ( find_case_insensitive_as( " AS x" ) . is_some( ) ) ;
166+ assert ! ( find_case_insensitive_as( "x AS " ) . is_some( ) ) ;
167+ }
168+
169+ #[ test]
170+ fn test_find_as_no_match ( ) {
171+ assert ! ( find_case_insensitive_as( "column something" ) . is_none( ) ) ;
172+ assert ! ( find_case_insensitive_as( "" ) . is_none( ) ) ;
173+ assert ! ( find_case_insensitive_as( "a s" ) . is_none( ) ) ;
174+ assert ! ( find_case_insensitive_as( "col AX x" ) . is_none( ) ) ;
175+ }
176+
177+ #[ test]
178+ fn test_find_as_with_table_column ( ) {
179+ let idx = find_case_insensitive_as ( "table.col as alias" ) . unwrap ( ) ;
180+ assert_eq ! ( & "table.col as alias" [ idx..idx + 2 ] , "as" ) ;
181+ }
182+
183+ // ------------------------------------------------------------
184+ // Tests for From<&str> for ColumnRef
185+ // ------------------------------------------------------------
186+ #[ test]
187+ fn test_column_ref_simple_column ( ) {
188+ let c = ColumnRef :: from ( "name" ) ;
189+ assert_eq ! ( c. table, None ) ;
190+ assert_eq ! ( c. column, "name" ) ;
191+ assert_eq ! ( c. alias, None ) ;
192+ }
193+
194+ #[ test]
195+ fn test_column_ref_table_column ( ) {
196+ let c = ColumnRef :: from ( "users.name" ) ;
197+ assert_eq ! ( c. table, Some ( "users" ) ) ;
198+ assert_eq ! ( c. column, "name" ) ;
199+ assert_eq ! ( c. alias, None ) ;
200+ }
201+
202+ #[ test]
203+ fn test_column_ref_with_alias_uppercase_as ( ) {
204+ let c = ColumnRef :: from ( "users.name AS n" ) ;
205+ assert_eq ! ( c. table, Some ( "users" ) ) ;
206+ assert_eq ! ( c. column, "name" ) ;
207+ assert_eq ! ( c. alias, Some ( "n" ) ) ;
208+ }
209+
210+ #[ test]
211+ fn test_column_ref_with_alias_lowercase_as ( ) {
212+ let c = ColumnRef :: from ( "users.name as n" ) ;
213+ assert_eq ! ( c. table, Some ( "users" ) ) ;
214+ assert_eq ! ( c. column, "name" ) ;
215+ assert_eq ! ( c. alias, Some ( "n" ) ) ;
216+ }
217+
218+ #[ test]
219+ fn test_column_ref_with_alias_mixed_case_as ( ) {
220+ let c = ColumnRef :: from ( "users.name As n" ) ;
221+ assert_eq ! ( c. table, Some ( "users" ) ) ;
222+ assert_eq ! ( c. column, "name" ) ;
223+ assert_eq ! ( c. alias, Some ( "n" ) ) ;
224+ }
225+
226+ #[ test]
227+ fn test_column_ref_multiple_spaces_around_as ( ) {
228+ let c = ColumnRef :: from ( "users.name AS n" ) ;
229+ assert_eq ! ( c. table, Some ( "users" ) ) ;
230+ assert_eq ! ( c. column, "name" ) ;
231+ assert_eq ! ( c. alias, Some ( "n" ) ) ;
232+ }
233+
234+ #[ test]
235+ fn test_column_ref_alias_without_table ( ) {
236+ let c = ColumnRef :: from ( "name AS n" ) ;
237+ assert_eq ! ( c. table, None ) ;
238+ assert_eq ! ( c. column, "name" ) ;
239+ assert_eq ! ( c. alias, Some ( "n" ) ) ;
240+ }
241+
242+ #[ test]
243+ fn test_column_ref_no_alias_when_as_not_valid ( ) {
244+ let c = ColumnRef :: from ( "nameASn" ) ;
245+ assert_eq ! ( c. table, None ) ;
246+ assert_eq ! ( c. column, "nameASn" ) ;
247+ assert_eq ! ( c. alias, None ) ;
248+ }
249+
250+ #[ test]
251+ fn test_column_ref_trim_whitespace ( ) {
252+ let c = ColumnRef :: from ( " users.name AS n " ) ;
253+ assert_eq ! ( c. table, Some ( "users" ) ) ;
254+ assert_eq ! ( c. column, "name" ) ;
255+ assert_eq ! ( c. alias, Some ( "n" ) ) ;
256+ }
257+
258+ #[ test]
259+ fn test_column_ref_alias_complex ( ) {
260+ let c = ColumnRef :: from ( "users.full_name AS fullNameAlias" ) ;
261+ assert_eq ! ( c. table, Some ( "users" ) ) ;
262+ assert_eq ! ( c. column, "full_name" ) ;
263+ assert_eq ! ( c. alias, Some ( "fullNameAlias" ) ) ;
264+ }
265+
266+ #[ test]
267+ fn test_column_ref_no_table_but_alias ( ) {
268+ let c = ColumnRef :: from ( "email AS e" ) ;
269+ assert_eq ! ( c. table, None ) ;
270+ assert_eq ! ( c. column, "email" ) ;
271+ assert_eq ! ( c. alias, Some ( "e" ) ) ;
272+ }
273+
274+ #[ test]
275+ fn test_column_ref_only_column_and_spaces ( ) {
276+ let c = ColumnRef :: from ( " column_name " ) ;
277+ assert_eq ! ( c. table, None ) ;
278+ assert_eq ! ( c. column, "column_name" ) ;
279+ assert_eq ! ( c. alias, None ) ;
280+ }
281+
282+ #[ test]
283+ fn test_column_ref_only_table_column_with_spaces ( ) {
284+ let c = ColumnRef :: from ( " users . name " ) ;
285+ assert_eq ! ( c. table, Some ( "users" ) ) ;
286+ assert_eq ! ( c. column, "name" ) ;
287+ assert_eq ! ( c. alias, None ) ;
288+ }
289+ }
0 commit comments