Adding typed Table<'r> which stores typed rows of the table and computes columns on demand.
This commit is contained in:
Родитель
c07196c05e
Коммит
6bc773f4e7
|
@ -26,6 +26,8 @@ let ``Combination of functions ToRows and OfRows returns a record array identica
|
|||
let table = Table.OfRows p
|
||||
let rows = table.ToRows<ValidTypesRec>() |> Seq.toArray
|
||||
Assert.AreEqual(p, rows)
|
||||
Assert.AreEqual(p, table.Rows |> Seq.toArray)
|
||||
Assert.AreEqual(table.RowsCount, table.Rows.Length)
|
||||
|
||||
|
||||
[<Test; Category("CI")>]
|
||||
|
@ -60,6 +62,8 @@ let ``Table.OfRows for a non-record``() =
|
|||
Assert.AreEqual(2, t.RowsCount, "rows count")
|
||||
Assert.AreEqual("A", t.[0].Rows.[0].AsString, "0,0")
|
||||
Assert.AreEqual("B", t.["Prop"].Rows.[1].AsString, "0,1")
|
||||
Assert.AreEqual("A", t.Rows.[0].Prop)
|
||||
Assert.AreEqual("B", t.Rows.[1].Prop)
|
||||
|
||||
[<Test; Category("CI"); ExpectedException(typeof<System.Collections.Generic.KeyNotFoundException>)>]
|
||||
let ``Table.ToRows fails when table has no column for a property``() =
|
||||
|
|
|
@ -120,9 +120,9 @@ type Column private (name:string, values: ColumnValues, height: int) =
|
|||
| t -> raise (new NotSupportedException(sprintf "Type '%A' is not a valid column type" t))
|
||||
Column(name, values, count)
|
||||
|
||||
type Table private (columns : Column list, height : int) =
|
||||
static let emptyTable = new Table(List.Empty)
|
||||
static let raiseDiffHeights() = invalidOp("Given columns are of different heights")
|
||||
type Table internal (columns : Column list, height : int) =
|
||||
static let emptyTable : Table = Table(List.Empty, 0)
|
||||
|
||||
static let assertAndGetHeight (columns:Column list) =
|
||||
match columns with
|
||||
| [] -> 0
|
||||
|
@ -155,11 +155,39 @@ type Table private (columns : Column list, height : int) =
|
|||
member x.GetEnumerator() : IEnumerator<Column> = (columns |> Seq.ofList).GetEnumerator()
|
||||
member x.GetEnumerator() : System.Collections.IEnumerator = ((columns |> Seq.ofList) :> System.Collections.IEnumerable).GetEnumerator()
|
||||
|
||||
member x.ToRows<'r>() : 'r seq =
|
||||
abstract ToRows<'r> : unit -> 'r seq
|
||||
default x.ToRows<'r>() : 'r seq = Table.ToRows x
|
||||
|
||||
override x.ToString() = String.Join("\n", columns |> Seq.map (fun c -> c.ToString()))
|
||||
|
||||
static member OfColumns (columns: Column seq) : Table = Table(columns)
|
||||
static member OfRows<'r> (rows : 'r seq) : Table<'r> = Table<'r>(rows |> ImmutableArray.CreateRange)
|
||||
static member OfRows<'r> (rows : ImmutableArray<'r>) : Table<'r> = Table<'r>(rows)
|
||||
|
||||
static member internal ColumnsOfRows<'r>(rows : ImmutableArray<'r>) : Column seq =
|
||||
let typeR = typeof<'r>
|
||||
let n = rows.Length
|
||||
let props =
|
||||
match typeR.GetCustomAttributes(typeof<CompilationMappingAttribute>, false)
|
||||
|> Seq.exists(fun attr -> (attr :?> CompilationMappingAttribute).SourceConstructFlags = SourceConstructFlags.RecordType) with
|
||||
| true -> // F# record
|
||||
Util.getRecordProperties typeR
|
||||
| false -> // non-record
|
||||
typeR.GetProperties(Reflection.BindingFlags.Instance ||| Reflection.BindingFlags.Public) |> Seq.filter (fun p -> p.CanRead)
|
||||
props |> Seq.map(fun p ->
|
||||
match p.PropertyType with
|
||||
| t when t = typeof<float> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,float>(rows, p), n)
|
||||
| t when t = typeof<int> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,int>(rows, p), n)
|
||||
| t when t = typeof<string> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,string>(rows, p), n)
|
||||
| t when t = typeof<DateTime> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,DateTime>(rows, p), n)
|
||||
| t when t = typeof<bool> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,bool>(rows, p), n)
|
||||
| t -> invalidArg "rows" (sprintf "Property '%s' has type '%A' which is not a valid table column type" p.Name t))
|
||||
|
||||
static member internal ToRows<'r>(t: Table) : 'r seq =
|
||||
let typeR = typeof<'r>
|
||||
let d_createR, props =
|
||||
match typeR.GetCustomAttributes(typeof<CompilationMappingAttribute>, false)
|
||||
|> Seq.exists(fun attr -> (attr :?> CompilationMappingAttribute).SourceConstructFlags = SourceConstructFlags.RecordType) with
|
||||
|> Seq.exists(fun attr -> (attr :?> CompilationMappingAttribute).SourceConstructFlags = SourceConstructFlags.RecordType) with
|
||||
| true -> // F# record
|
||||
let props = Util.getRecordProperties typeR |> Seq.map(fun p -> p.Name, p.PropertyType) |> Seq.toArray
|
||||
let ctor = typeR.GetConstructor(props |> Array.map snd)
|
||||
|
@ -177,39 +205,12 @@ type Table private (columns : Column list, height : int) =
|
|||
Expression.Block(
|
||||
[l_r],
|
||||
Seq.concat
|
||||
[seq{ yield Expression.Assign(l_r, Expression.New(ctor)) :> Expression }
|
||||
Seq.zip props l_pars |> Seq.map(fun (p, l_p) -> Expression.Assign(Expression.Property(l_r, p), l_p) :> Expression)
|
||||
seq{ yield upcast l_r }]),
|
||||
[ seq{ yield Expression.Assign(l_r, Expression.New(ctor)) :> Expression }
|
||||
Seq.zip props l_pars |> Seq.map(fun (p, l_p) -> Expression.Assign(Expression.Property(l_r, p), l_p) :> Expression)
|
||||
seq{ yield upcast l_r }]),
|
||||
l_pars)
|
||||
l.Compile(), props |> Array.map(fun p -> p.Name)
|
||||
Table.Mapd (props |> Array.map (fun name -> x.[name])) d_createR x
|
||||
|
||||
override x.ToString() = String.Join("\n", columns |> Seq.map (fun c -> c.ToString()))
|
||||
|
||||
static member OfColumns (columns: Column seq) = Table(columns)
|
||||
|
||||
static member OfRows<'r>(rows : 'r seq) =
|
||||
let typeR = typeof<'r>
|
||||
let rows_a = rows |> Seq.toArray
|
||||
let n = rows_a.Length
|
||||
let props =
|
||||
match typeR.GetCustomAttributes(typeof<CompilationMappingAttribute>, false)
|
||||
|> Seq.exists(fun attr -> (attr :?> CompilationMappingAttribute).SourceConstructFlags = SourceConstructFlags.RecordType) with
|
||||
| true -> // F# record
|
||||
Util.getRecordProperties typeR
|
||||
| false -> // non-record
|
||||
typeR.GetProperties(Reflection.BindingFlags.Instance ||| Reflection.BindingFlags.Public) |> Seq.filter (fun p -> p.CanRead)
|
||||
let columns =
|
||||
props
|
||||
|> Seq.map(fun p ->
|
||||
match p.PropertyType with
|
||||
| t when t = typeof<float> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,float>(rows_a, p), n)
|
||||
| t when t = typeof<int> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,int>(rows_a, p), n)
|
||||
| t when t = typeof<string> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,string>(rows_a, p), n)
|
||||
| t when t = typeof<DateTime> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,DateTime>(rows_a, p), n)
|
||||
| t when t = typeof<bool> -> Column.OfLazyArray(p.Name, arrayOfProp<'r,bool>(rows_a, p), n)
|
||||
| t -> invalidArg "rows" (sprintf "Property '%s' has type '%A' which is not a valid table column type" p.Name t))
|
||||
Table(columns)
|
||||
Table.Mapd (props |> Array.map (fun name -> t.[name])) d_createR t
|
||||
|
||||
static member Empty: Table = emptyTable
|
||||
|
||||
|
@ -339,7 +340,6 @@ type Table private (columns : Column list, height : int) =
|
|||
let deleg = Funcs.toDelegate transform
|
||||
let colArrays = cs |> Array.map(fun n -> table.[n].Rows.ToUntypedList() |> box)
|
||||
deleg.DynamicInvoke(colArrays) :?> 'c
|
||||
|
||||
|
||||
static member Load (reader:System.IO.TextReader, settings:Angara.Data.DelimitedFile.ReadSettings) : Table =
|
||||
let cols =
|
||||
|
@ -352,7 +352,7 @@ type Table private (columns : Column list, height : int) =
|
|||
| Angara.Data.DelimitedFile.ColumnType.Boolean -> Column.OfArray (schema.Name, data :?> ImmutableArray<bool>)
|
||||
| Angara.Data.DelimitedFile.ColumnType.DateTime -> Column.OfArray (schema.Name, data :?> ImmutableArray<DateTime>)
|
||||
| Angara.Data.DelimitedFile.ColumnType.String -> Column.OfArray (schema.Name, data :?> ImmutableArray<string>))
|
||||
new Table(cols |> Seq.toList)
|
||||
Table(cols |> Seq.toList)
|
||||
static member Load (reader:System.IO.TextReader) : Table =
|
||||
Table.Load (reader, Angara.Data.DelimitedFile.ReadSettings.Default)
|
||||
static member Load (path: string, settings:Angara.Data.DelimitedFile.ReadSettings) : Table =
|
||||
|
@ -371,4 +371,19 @@ type Table private (columns : Column list, height : int) =
|
|||
use writer = System.IO.File.CreateText path
|
||||
Table.Save (table, writer, settings)
|
||||
static member Save (table:Table, path: string) : unit =
|
||||
Table.Save (table, path, Angara.Data.DelimitedFile.WriteSettings.Default)
|
||||
Table.Save (table, path, Angara.Data.DelimitedFile.WriteSettings.Default)
|
||||
|
||||
and Table<'r>(rows : ImmutableArray<'r>) =
|
||||
inherit Table(Table.ColumnsOfRows rows |> Seq.toList, rows.Length)
|
||||
|
||||
member x.Rows : ImmutableArray<'r> = rows
|
||||
|
||||
override x.ToRows<'s>() : 's seq =
|
||||
// Implementation issue: cannot invoke base.ToRows<'s>() here because of undocumented (?) F# compiler property;
|
||||
// if I don't use the mutual types recursion (i.e. "and Table<'r>"), "base.ToRows" works well here; but then I cannot
|
||||
// create Table<'r> instances from the static methods "OfRows" of the Table type.
|
||||
// That's why I call static method Table.ToRows<'s>() instead of base.ToRows<'s>().
|
||||
match typeof<'s> with
|
||||
| t when t = typeof<'r> -> rows |> coerce
|
||||
| _ -> Table.ToRows<'s>(x)
|
||||
|
||||
|
|
|
@ -38,8 +38,7 @@ type ColumnValues =
|
|||
member Item : rowIndex:int -> DataValue
|
||||
|
||||
/// Represents a table column which is a pair of column name and an immutable array of one of the supported types.
|
||||
[<Class>]
|
||||
type Column =
|
||||
type [<Class>] Column =
|
||||
member Name : string with get
|
||||
/// Returns column values.
|
||||
member Rows : ColumnValues with get
|
||||
|
@ -54,8 +53,7 @@ type Column =
|
|||
|
||||
/// Represents a table wich is an immutable list of named columns.
|
||||
/// The type is thread safe.
|
||||
[<Class>]
|
||||
type Table =
|
||||
type [<Class>] Table =
|
||||
interface IEnumerable<Column>
|
||||
|
||||
/// Gets a count of the total number of columns in the table.
|
||||
|
@ -84,7 +82,7 @@ type Table =
|
|||
/// The method uses reflection to build instances of `'r` from the table columns:
|
||||
/// - If `'r` is F# record, then for each property of the type there must be a corresponding column of identical type.
|
||||
/// - Otherwise, then `'r` has default constructor and for each public writable property there must be a column of same name and type as the property.
|
||||
member ToRows<'r> : unit -> 'r seq
|
||||
abstract ToRows<'r> : unit -> 'r seq
|
||||
|
||||
/// Builds a table from a finite sequence of columns.
|
||||
/// All given columns must be of same height.
|
||||
|
@ -97,7 +95,13 @@ type Table =
|
|||
/// each table row corresponds to an element of the input sequence with the order respected.
|
||||
/// If the type `'r` is an F# record, the order of columns is identical to the record properties order.
|
||||
/// If there is a public property having a type that is not valid for a table column, the function fails with an exception.
|
||||
static member OfRows<'r> : 'r seq -> Table
|
||||
static member OfRows<'r> : 'r seq -> Table<'r>
|
||||
/// Builds a table such that each public property of a given type `'r`
|
||||
/// becomes the table column with the name and type identical to the property;
|
||||
/// each table row corresponds to an element of the input sequence with the order respected.
|
||||
/// If the type `'r` is an F# record, the order of columns is identical to the record properties order.
|
||||
/// If there is a public property having a type that is not valid for a table column, the function fails with an exception.
|
||||
static member OfRows<'r> : ImmutableArray<'r> -> Table<'r>
|
||||
|
||||
/// Creates a new, empty table
|
||||
static member Empty : Table
|
||||
|
@ -225,12 +229,7 @@ type Table =
|
|||
/// Saves the table to a delimited text stream using given writer.
|
||||
static member Save : table:Table * writer:System.IO.TextWriter * settings:Angara.Data.DelimitedFile.WriteSettings -> unit
|
||||
|
||||
|
||||
//[<Class>]
|
||||
//type Table<'r> =
|
||||
// inherit Table
|
||||
//
|
||||
// new : rows : 'r seq -> Table<'r>
|
||||
//
|
||||
// member Rows : ImmutableArray<'r>
|
||||
|
||||
and [<Class>] Table<'r> =
|
||||
inherit Table
|
||||
new : rows:ImmutableArray<'r> -> Table<'r>
|
||||
member Rows : ImmutableArray<'r>
|
|
@ -28,6 +28,8 @@ let inline invalidCast message = raise (new System.InvalidCastException(message)
|
|||
|
||||
let inline notFound message = raise (new System.Collections.Generic.KeyNotFoundException(message))
|
||||
|
||||
let inline raiseDiffHeights() = invalidOp("Given columns are of different heights")
|
||||
|
||||
let getRecordProperties (typeR : System.Type) =
|
||||
typeR.GetProperties()
|
||||
|> Seq.choose(fun p ->
|
||||
|
@ -37,7 +39,7 @@ let getRecordProperties (typeR : System.Type) =
|
|||
|> Seq.sortBy snd
|
||||
|> Seq.map fst
|
||||
|
||||
let arrayOfProp<'r,'p> (rows: 'r[], p:System.Reflection.PropertyInfo) =
|
||||
let arrayOfProp<'r,'p> (rows: ImmutableArray<'r>, p:System.Reflection.PropertyInfo) =
|
||||
lazy(
|
||||
let n = rows.Length
|
||||
let bld = ImmutableArray.CreateBuilder<'p>(n)
|
||||
|
|
Загрузка…
Ссылка в новой задаче