@@ -2,14 +2,16 @@ use std::{collections::BTreeSet, sync::Arc};
22
33use diesel:: { debug_query, pg:: Pg } ;
44use graph:: {
5+ data_source:: CausalityRegion ,
56 prelude:: { r, serde_json as json, DeploymentHash , EntityFilter } ,
67 schema:: InputSchema ,
78} ;
89
910use crate :: {
11+ block_range:: BoundSide ,
1012 layout_for_tests:: { make_dummy_site, Namespace } ,
1113 relational:: { Catalog , ColumnType , Layout } ,
12- relational_queries:: FromColumnValue ,
14+ relational_queries:: { FindRangeQuery , FromColumnValue } ,
1315} ;
1416
1517use crate :: relational_queries:: Filter ;
@@ -86,3 +88,112 @@ fn prefix() {
8688 let filter = EntityFilter :: In ( "address" . to_string ( ) , vec ! [ "0xbeef" . into( ) ] ) ;
8789 filter_contains ( filter, r#"substring(c."address", 1, 64) in ($1)"# ) ;
8890}
91+
92+ #[ test]
93+ fn find_range_query_id_type_casting ( ) {
94+ let string_schema = "
95+ type StringEntity @entity {
96+ id: String!,
97+ name: String
98+ }" ;
99+
100+ let bytes_schema = "
101+ type BytesEntity @entity {
102+ id: Bytes!,
103+ address: Bytes
104+ }" ;
105+
106+ let int8_schema = "
107+ type Int8Entity @entity {
108+ id: Int8!,
109+ value: Int8
110+ }" ;
111+
112+ let string_layout = test_layout ( string_schema) ;
113+ let bytes_layout = test_layout ( bytes_schema) ;
114+ let int8_layout = test_layout ( int8_schema) ;
115+
116+ let string_table = string_layout
117+ . table_for_entity (
118+ & string_layout
119+ . input_schema
120+ . entity_type ( "StringEntity" )
121+ . unwrap ( ) ,
122+ )
123+ . unwrap ( ) ;
124+ let bytes_table = bytes_layout
125+ . table_for_entity (
126+ & bytes_layout
127+ . input_schema
128+ . entity_type ( "BytesEntity" )
129+ . unwrap ( ) ,
130+ )
131+ . unwrap ( ) ;
132+ let int8_table = int8_layout
133+ . table_for_entity ( & int8_layout. input_schema . entity_type ( "Int8Entity" ) . unwrap ( ) )
134+ . unwrap ( ) ;
135+
136+ let causality_region = CausalityRegion :: ONCHAIN ;
137+ let bound_side = BoundSide :: Lower ;
138+ let block_range = 100 ..200 ;
139+
140+ test_id_type_casting (
141+ string_table. as_ref ( ) ,
142+ "id::bytea" ,
143+ "String ID should be cast to bytea" ,
144+ ) ;
145+ test_id_type_casting ( bytes_table. as_ref ( ) , "id" , "Bytes ID should remain as id" ) ;
146+ test_id_type_casting (
147+ int8_table. as_ref ( ) ,
148+ "id::text::bytea" ,
149+ "Int8 ID should be cast to text then bytea" ,
150+ ) ;
151+
152+ let tables = vec ! [
153+ string_table. as_ref( ) ,
154+ bytes_table. as_ref( ) ,
155+ int8_table. as_ref( ) ,
156+ ] ;
157+ let query = FindRangeQuery :: new ( & tables, causality_region, bound_side, block_range) ;
158+ let sql = debug_query :: < Pg , _ > ( & query) . to_string ( ) ;
159+
160+ assert ! (
161+ sql. contains( "id::bytea" ) ,
162+ "String entity ID casting should be present in UNION query"
163+ ) ;
164+ assert ! (
165+ sql. contains( "id as id" ) ,
166+ "Bytes entity ID should be present in UNION query"
167+ ) ;
168+ assert ! (
169+ sql. contains( "id::text::bytea" ) ,
170+ "Int8 entity ID casting should be present in UNION query"
171+ ) ;
172+
173+ assert ! (
174+ sql. contains( "union all" ) ,
175+ "Multiple tables should generate UNION ALL queries"
176+ ) ;
177+ assert ! (
178+ sql. contains( "order by block_number, entity, id" ) ,
179+ "Query should end with proper ordering"
180+ ) ;
181+ }
182+
183+ fn test_id_type_casting ( table : & crate :: relational:: Table , expected_cast : & str , test_name : & str ) {
184+ let causality_region = CausalityRegion :: ONCHAIN ;
185+ let bound_side = BoundSide :: Lower ;
186+ let block_range = 100 ..200 ;
187+
188+ let tables = vec ! [ table] ;
189+ let query = FindRangeQuery :: new ( & tables, causality_region, bound_side, block_range) ;
190+ let sql = debug_query :: < Pg , _ > ( & query) . to_string ( ) ;
191+
192+ assert ! (
193+ sql. contains( expected_cast) ,
194+ "{}: Expected '{}' in SQL, got: {}" ,
195+ test_name,
196+ expected_cast,
197+ sql
198+ ) ;
199+ }
0 commit comments