3
3
module rec OpenDiffix.Core.CommonTypes
4
4
5
5
open System
6
+ open System.Diagnostics
7
+ open System.Runtime .CompilerServices
6
8
7
9
// ----------------------------------------------------------------
8
10
// Values
@@ -165,19 +167,14 @@ type QueryContext =
165
167
{
166
168
AnonymizationParams: AnonymizationParams
167
169
DataProvider: IDataProvider
170
+ Metadata: QueryMetadata
168
171
}
169
- static member Default =
170
- {
171
- AnonymizationParams = AnonymizationParams.Default
172
- DataProvider =
173
- { new IDataProvider with
174
- member _.OpenTable ( _table , _columnIndices ) = failwith " No tables in data provider"
175
- member _.GetSchema () = []
176
- member _.Dispose () = ()
177
- }
178
- }
179
172
180
- type NoiseLayers = { BucketSeed: Hash }
173
+ type NoiseLayers =
174
+ {
175
+ BucketSeed: Hash
176
+ }
177
+ static member Default = { BucketSeed = 0 UL }
181
178
182
179
type ExecutionContext =
183
180
{
@@ -186,4 +183,215 @@ type ExecutionContext =
186
183
}
187
184
member this.AnonymizationParams = this.QueryContext.AnonymizationParams
188
185
member this.DataProvider = this.QueryContext.DataProvider
189
- static member Default = { QueryContext = QueryContext.Default; NoiseLayers = { BucketSeed = 0 UL } }
186
+ member this.Metadata = this.QueryContext.Metadata
187
+
188
+ // ----------------------------------------------------------------
189
+ // Constants
190
+ // ----------------------------------------------------------------
191
+
192
+ let NULL_TYPE = UnknownType " null_type"
193
+ let MISSING_TYPE = UnknownType " missing_type"
194
+ let MIXED_TYPE = UnknownType " mixed_type"
195
+
196
+ // ----------------------------------------------------------------
197
+ // Functions
198
+ // ----------------------------------------------------------------
199
+
200
+ module Row =
201
+ let equalityComparer = LanguagePrimitives.FastGenericEqualityComparer< Row>
202
+
203
+ module ExpressionType =
204
+ /// Resolves the common type from a list of types.
205
+ let commonType types =
206
+ types
207
+ |> List.distinct
208
+ |> function
209
+ | [] -> MISSING_ TYPE
210
+ | [ t ] -> t
211
+ | _ -> MIXED_ TYPE
212
+
213
+ module Function =
214
+ let fromString name =
215
+ match name with
216
+ | " count" -> AggregateFunction( Count, AggregateOptions.Default)
217
+ | " sum" -> AggregateFunction( Sum, AggregateOptions.Default)
218
+ | " diffix_count" -> AggregateFunction( DiffixCount, AggregateOptions.Default)
219
+ | " diffix_low_count" -> AggregateFunction( DiffixLowCount, AggregateOptions.Default)
220
+ | " +" -> ScalarFunction Add
221
+ | " -" -> ScalarFunction Subtract
222
+ | " *" -> ScalarFunction Multiply
223
+ | " /" -> ScalarFunction Divide
224
+ | " %" -> ScalarFunction Modulo
225
+ | " round" -> ScalarFunction Round
226
+ | " ceil" -> ScalarFunction Ceil
227
+ | " floor" -> ScalarFunction Floor
228
+ | " round_by" -> ScalarFunction RoundBy
229
+ | " ceil_by" -> ScalarFunction CeilBy
230
+ | " floor_by" -> ScalarFunction FloorBy
231
+ | " abs" -> ScalarFunction Abs
232
+ | " length" -> ScalarFunction Length
233
+ | " lower" -> ScalarFunction Lower
234
+ | " upper" -> ScalarFunction Upper
235
+ | " substring" -> ScalarFunction Substring
236
+ | " ||" -> ScalarFunction Concat
237
+ | " width_bucket" -> ScalarFunction WidthBucket
238
+ | " cast" -> ScalarFunction Cast
239
+ | other -> failwith $" Unknown function `{other}`"
240
+
241
+ module Table =
242
+ /// Finds a column along with its index. The index is zero-based.
243
+ let tryFindColumn table columnName =
244
+ table.Columns
245
+ |> List.indexed
246
+ |> List.tryFind ( fun ( _index , column ) -> String.equalsI column.Name columnName)
247
+
248
+ /// Finds a column along with its index. The index is zero-based. Fails if column not found.
249
+ let findColumn table columnName =
250
+ columnName
251
+ |> tryFindColumn table
252
+ |> function
253
+ | Some column -> column
254
+ | None -> failwith $" Could not find column `{columnName}` in table `{table.Name}`"
255
+
256
+ module Schema =
257
+ /// Finds a table by name in the schema.
258
+ let tryFindTable schema tableName =
259
+ schema |> List.tryFind ( fun table -> String.equalsI table.Name tableName)
260
+
261
+ /// Finds a table by name in the schema. Fails if table not found.
262
+ let findTable schema tableName =
263
+ tableName
264
+ |> tryFindTable schema
265
+ |> function
266
+ | Some table -> table
267
+ | None -> failwith $" Could not find table `{tableName}`."
268
+
269
+ module AnonymizationParams =
270
+ /// Returns whether the given column in the table is an AID column.
271
+ let isAidColumn anonParams tableName columnName =
272
+ anonParams.TableSettings
273
+ |> Map.tryFind tableName
274
+ |> function
275
+ | Some tableSettings -> tableSettings.AidColumns |> List.exists ( String.equalsI columnName)
276
+ | None -> false
277
+
278
+ module QueryContext =
279
+ let private defaultDataProvider =
280
+ { new IDataProvider with
281
+ member _.OpenTable ( _table , _columnIndices ) = failwith " No tables in data provider"
282
+ member _.GetSchema () = []
283
+ member _.Dispose () = ()
284
+ }
285
+
286
+ let make anonParams dataProvider =
287
+ {
288
+ AnonymizationParams = anonParams
289
+ DataProvider = dataProvider
290
+ Metadata = QueryMetadata( fun _msg -> ())
291
+ }
292
+
293
+ let makeDefault () =
294
+ make AnonymizationParams.Default defaultDataProvider
295
+
296
+ let makeWithAnonParams anonParams = make anonParams defaultDataProvider
297
+
298
+ let makeWithDataProvider dataProvider =
299
+ make AnonymizationParams.Default dataProvider
300
+
301
+ let withLogger logger queryContext =
302
+ { queryContext with Metadata = QueryMetadata( logger) }
303
+
304
+ module ExecutionContext =
305
+ let fromQueryContext queryContext =
306
+ { QueryContext = queryContext; NoiseLayers = NoiseLayers.Default }
307
+
308
+ let makeDefault () =
309
+ fromQueryContext ( QueryContext.makeDefault ())
310
+
311
+ // ----------------------------------------------------------------
312
+ // Logging & Instrumentation
313
+ // ----------------------------------------------------------------
314
+
315
+ // Events are relative to init time, expressed in ticks (unit of 100ns).
316
+ type Ticks = int64
317
+
318
+ type LogLevel =
319
+ | DebugLevel
320
+ | InfoLevel
321
+ | WarningLevel
322
+
323
+ type LogMessage = { Timestamp: Ticks ; Level: LogLevel ; Message: string }
324
+
325
+ module Ticks =
326
+ let private ticksPerMillisecond = float TimeSpan.TicksPerMillisecond
327
+
328
+ let toTimestamp ( t : Ticks ) = TimeSpan.FromTicks( t) .ToString( " c" )
329
+
330
+ let toDuration ( t : Ticks ) =
331
+ let ms = ( float t / ticksPerMillisecond)
332
+
333
+ if ms >= 1000.0 then
334
+ ( ms / 1000.0 ) .ToString( " N3" ) + " s"
335
+ else
336
+ ms.ToString( " N3" ) + " ms"
337
+
338
+ module LogMessage =
339
+ let private levelToString =
340
+ function
341
+ | DebugLevel -> " [DBG]"
342
+ | InfoLevel -> " [INF]"
343
+ | WarningLevel -> " [WRN]"
344
+
345
+ let toString ( message : LogMessage ) : string =
346
+ $" {Ticks.toTimestamp message.Timestamp} {levelToString message.Level} {message.Message}"
347
+
348
+ type LoggerCallback = LogMessage -> unit
349
+
350
+ type QueryMetadata ( logger : LoggerCallback ) =
351
+ let globalTimer = Stopwatch.StartNew()
352
+ let measurements = Collections.Generic.Dictionary< string, Ticks>()
353
+ let counters = Collections.Generic.Dictionary< string, int>()
354
+
355
+ let makeMessage level message =
356
+ { Timestamp = globalTimer.Elapsed.Ticks; Level = level; Message = message }
357
+
358
+ [<Conditional( " DEBUG" ) >]
359
+ member this.LogDebug ( message : string ) : unit = logger ( makeMessage DebugLevel message)
360
+
361
+ member this.Log ( message : string ) : unit = logger ( makeMessage InfoLevel message)
362
+
363
+ member this.LogWarning ( message : string ) : unit =
364
+ logger ( makeMessage WarningLevel message)
365
+
366
+ member this.MeasureScope ( [<CallerMemberName>] ? event : string ) : IDisposable =
367
+ let event = event.Value
368
+ let stopwatch = Stopwatch.StartNew()
369
+
370
+ { new IDisposable with
371
+ member _.Dispose () =
372
+ let total = stopwatch.Elapsed.Ticks + ( Dictionary.getOrDefault event 0 L measurements)
373
+ stopwatch.Reset()
374
+ measurements.[ event] <- total
375
+ }
376
+
377
+ [<Conditional( " DEBUG" ) >]
378
+ member this.CountDebug ( [<CallerMemberName>] ? event : string ) : unit = this.Count( event.Value)
379
+
380
+ member this.Count ( [<CallerMemberName>] ? event : string ) : unit =
381
+ let event = event.Value
382
+ let currentCount = Dictionary.getOrDefault event 0 counters
383
+ counters.[ event] <- currentCount + 1
384
+
385
+ override this.ToString () =
386
+ let builder = Text.StringBuilder()
387
+
388
+ if measurements.Count > 0 || counters.Count > 0 then
389
+ builder.AppendLine( " <Metadata>" ) |> ignore
390
+
391
+ measurements
392
+ |> Seq.iter ( fun pair -> builder.AppendLine( $" {pair.Key}: {Ticks.toDuration pair.Value}" ) |> ignore)
393
+
394
+ counters
395
+ |> Seq.iter ( fun pair -> builder.AppendLine( $" {pair.Key}: {pair.Value}" ) |> ignore)
396
+
397
+ builder.ToString()
0 commit comments