@@ -38,15 +38,43 @@ pub mod offline {
38
38
use super :: QueryData ;
39
39
use crate :: database:: DatabaseExt ;
40
40
41
- use std:: fmt:: { self , Formatter } ;
42
- use std:: fs:: File ;
43
- use std:: io:: { BufReader , BufWriter } ;
44
- use std:: path:: Path ;
41
+ use std:: collections:: BTreeMap ;
42
+ use std:: fs:: { self , File } ;
43
+ use std:: io:: BufWriter ;
44
+ use std:: path:: { Path , PathBuf } ;
45
+ use std:: sync:: Mutex ;
45
46
47
+ use once_cell:: sync:: Lazy ;
46
48
use proc_macro2:: Span ;
47
- use serde:: de:: { Deserializer , IgnoredAny , MapAccess , Visitor } ;
48
49
use sqlx_core:: describe:: Describe ;
49
50
51
+ static OFFLINE_DATA_CACHE : Lazy < Mutex < BTreeMap < PathBuf , OfflineData > > > =
52
+ Lazy :: new ( || Mutex :: new ( BTreeMap :: new ( ) ) ) ;
53
+
54
+ #[ derive( serde:: Deserialize ) ]
55
+ struct BaseQuery {
56
+ query : String ,
57
+ describe : serde_json:: Value ,
58
+ }
59
+
60
+ #[ derive( serde:: Deserialize ) ]
61
+ struct OfflineData {
62
+ db : String ,
63
+ #[ serde( flatten) ]
64
+ hash_to_query : BTreeMap < String , BaseQuery > ,
65
+ }
66
+
67
+ impl OfflineData {
68
+ fn get_query_from_hash ( & self , hash : & str ) -> Option < DynQueryData > {
69
+ self . hash_to_query . get ( hash) . map ( |base_query| DynQueryData {
70
+ db_name : self . db . clone ( ) ,
71
+ query : base_query. query . to_owned ( ) ,
72
+ describe : base_query. describe . to_owned ( ) ,
73
+ hash : hash. to_owned ( ) ,
74
+ } )
75
+ }
76
+ }
77
+
50
78
#[ derive( serde:: Deserialize ) ]
51
79
pub struct DynQueryData {
52
80
#[ serde( skip) ]
@@ -61,15 +89,44 @@ pub mod offline {
61
89
/// Find and deserialize the data table for this query from a shared `sqlx-data.json`
62
90
/// file. The expected structure is a JSON map keyed by the SHA-256 hash of queries in hex.
63
91
pub fn from_data_file ( path : impl AsRef < Path > , query : & str ) -> crate :: Result < Self > {
64
- let this = serde_json:: Deserializer :: from_reader ( BufReader :: new (
65
- File :: open ( path. as_ref ( ) ) . map_err ( |e| {
66
- format ! ( "failed to open path {}: {}" , path. as_ref( ) . display( ) , e)
67
- } ) ?,
68
- ) )
69
- . deserialize_map ( DataFileVisitor {
70
- query,
71
- hash : hash_string ( query) ,
72
- } ) ?;
92
+ let path = path. as_ref ( ) ;
93
+
94
+ let query_data = {
95
+ let mut cache = OFFLINE_DATA_CACHE
96
+ . lock ( )
97
+ // Just reset the cache on error
98
+ . unwrap_or_else ( |posion_err| {
99
+ let mut guard = posion_err. into_inner ( ) ;
100
+ * guard = BTreeMap :: new ( ) ;
101
+ guard
102
+ } ) ;
103
+
104
+ if !cache. contains_key ( path) {
105
+ let offline_data_contents = fs:: read_to_string ( path)
106
+ . map_err ( |e| format ! ( "failed to read path {}: {}" , path. display( ) , e) ) ?;
107
+ let offline_data: OfflineData = serde_json:: from_str ( & offline_data_contents) ?;
108
+ let _ = cache. insert ( path. to_owned ( ) , offline_data) ;
109
+ }
110
+
111
+ let offline_data = cache
112
+ . get ( path)
113
+ . expect ( "Missing data should have just been added" ) ;
114
+
115
+ let query_hash = hash_string ( query) ;
116
+ let query_data = offline_data
117
+ . get_query_from_hash ( & query_hash)
118
+ . ok_or_else ( || format ! ( "failed to find data for query {}" , query) ) ?;
119
+
120
+ if query != query_data. query {
121
+ return Err ( format ! (
122
+ "hash collision for stored queryies:\n {:?}\n {:?}" ,
123
+ query, query_data. query
124
+ )
125
+ . into ( ) ) ;
126
+ }
127
+
128
+ query_data
129
+ } ;
73
130
74
131
#[ cfg( procmacr2_semver_exempt) ]
75
132
{
@@ -84,7 +141,7 @@ pub mod offline {
84
141
proc_macro:: tracked_path:: path ( path) ;
85
142
}
86
143
87
- Ok ( this )
144
+ Ok ( query_data )
88
145
}
89
146
}
90
147
@@ -138,67 +195,4 @@ pub mod offline {
138
195
139
196
hex:: encode ( Sha256 :: digest ( query. as_bytes ( ) ) )
140
197
}
141
-
142
- // lazily deserializes only the `QueryData` for the query we're looking for
143
- struct DataFileVisitor < ' a > {
144
- query : & ' a str ,
145
- hash : String ,
146
- }
147
-
148
- impl < ' de > Visitor < ' de > for DataFileVisitor < ' _ > {
149
- type Value = DynQueryData ;
150
-
151
- fn expecting ( & self , f : & mut Formatter ) -> fmt:: Result {
152
- write ! ( f, "expected map key {:?} or \" db\" " , self . hash)
153
- }
154
-
155
- fn visit_map < A > ( self , mut map : A ) -> Result < Self :: Value , <A as MapAccess < ' de > >:: Error >
156
- where
157
- A : MapAccess < ' de > ,
158
- {
159
- let mut db_name: Option < String > = None ;
160
-
161
- let query_data = loop {
162
- // unfortunately we can't avoid this copy because deserializing from `io::Read`
163
- // doesn't support deserializing borrowed values
164
- let key = map. next_key :: < String > ( ) ?. ok_or_else ( || {
165
- serde:: de:: Error :: custom ( format_args ! (
166
- "failed to find data for query {}" ,
167
- self . hash
168
- ) )
169
- } ) ?;
170
-
171
- // lazily deserialize the query data only
172
- if key == "db" {
173
- db_name = Some ( map. next_value :: < String > ( ) ?) ;
174
- } else if key == self . hash {
175
- let db_name = db_name. ok_or_else ( || {
176
- serde:: de:: Error :: custom ( "expected \" db\" key before query hash keys" )
177
- } ) ?;
178
-
179
- let mut query_data: DynQueryData = map. next_value ( ) ?;
180
-
181
- if query_data. query == self . query {
182
- query_data. db_name = db_name;
183
- query_data. hash = self . hash . clone ( ) ;
184
- break query_data;
185
- } else {
186
- return Err ( serde:: de:: Error :: custom ( format_args ! (
187
- "hash collision for stored queries:\n {:?}\n {:?}" ,
188
- self . query, query_data. query
189
- ) ) ) ;
190
- } ;
191
- } else {
192
- // we don't care about entries that don't match our hash
193
- let _ = map. next_value :: < IgnoredAny > ( ) ?;
194
- }
195
- } ;
196
-
197
- // Serde expects us to consume the whole map; fortunately they've got a convenient
198
- // type to let us do just that
199
- while let Some ( _) = map. next_entry :: < IgnoredAny , IgnoredAny > ( ) ? { }
200
-
201
- Ok ( query_data)
202
- }
203
- }
204
198
}
0 commit comments