估计阅读时长: 5 分钟

https://github.com/xieguigang/Darwinism

对于LINQ数据查询引擎而言,其可以接收任意类型的数据源,进行数据查询。只要存在有相对应的数据源驱动程序即可。

在之前的博客文章《【Darwinism】LINQ数据查询脚本引擎开发》我提到过,我们可以为Sqlite数据库编写数据驱动程序,这样子我们就可以使用LINQ脚本查询引擎进行Sqlite数据库的查询分析操作了。在这里,我们就来介绍一下,我是如何为LINQ查询脚本引擎编写Sqlite数据库的数据源驱动程序来完成LINQ脚本对Sqlite数据库的查询分析的。

通过R#脚本浏览Sqlite数据库

在执行LINQ脚本对Sqlite数据库的查询之前,我们首先使用R#环境之中所提供的开发者工具来看一下我们的数据库文件里面有哪些数据吧。在R#脚本之中,我们可以通过系统自带的sqlite程序包进行数据库文件内容的查看,例如:

imports "sqlite" from "devkit";

using xcc as sqlite::open(`${dirname(@script)}/xcc.db`) {
    xcc
    |> sqlite::ls
    |> print()
    ;

    for(name in ["Pathways", "GenePathways"]) {
        print("view data contents of table:");
        print(name);

        xcc
        |> sqlite::load(name)
        |> head
        |> print
        ;
    }
}

可以看到,脚本帮我们把数据库之中的所有数据表的表名称都打印出来了。其中,有一个名称为GenePathways的数据表,待会我们就使用这个数据表进行LINQ查询脚本的测试。

[1] "view data contents of table:"
[1]     "GenePathways"
        gid            pid
<mode>  <string>       <integer>
[1, ]   "xcc:XCC0734"  551
[2, ]   "xcc:XCC0188"  11
[3, ]   "xcc:XCC2008"  171
[4, ]   "xcc:XCC1212"  1091
[5, ]   "xcc:XCC1722"  41
[6, ]   "xcc:XCC2712"  121

LINQ脚本数据源驱动程序的创建

LINQ脚本查询引擎可以通过插件的形式,加载外部的数据源驱动程序,从而能够查询任意来源的数据对象。在LINQ查询引擎中,所有的数据源驱动程序模块都会需要继承DataSourceDriver对象,并实现一个用于加载数据源的迭代器函数:

Public MustInherit Class DataSourceDriver

    Protected ReadOnly arguments As String()

    Sub New(arguments As String())
        Me.arguments = arguments
    End Sub

    Public MustOverride Function ReadFromUri(uri As String) As IEnumerable(Of Object)

End Class

如果你希望为这个LINQ查询引擎编写自己的数据源驱动程序,你需要完成下面的两个操作步骤:

例如,在这里实现的就是我们需要进行Sqlite数据库查询用到的Sqlite数据表加载过程

Imports LINQ.Runtime.Drivers
Imports Microsoft.VisualBasic.Data.IO.ManagedSqlite.Core
Imports Microsoft.VisualBasic.Data.IO.ManagedSqlite.Core.SQLSchema
Imports Microsoft.VisualBasic.Data.IO.ManagedSqlite.Core.Tables
Imports Microsoft.VisualBasic.My.JavaScript

<DriverFlag("table")>
Public Class TableReader : Inherits DataSourceDriver

    Public Sub New(arguments() As String)
        MyBase.New(arguments)
    End Sub

    Public Overrides Iterator Function ReadFromUri(uri As String) As IEnumerable(Of Object)
        Dim tableName As String = arguments(Scan0)
        Dim sqlite As Sqlite3Database = Sqlite3Database.OpenFile(dbFile:=uri)
        Dim rawRef As Sqlite3Table = sqlite.GetTable(tableName)
        Dim rows As Sqlite3Row() = rawRef.EnumerateRows.ToArray
        Dim schema As Schema = rawRef.SchemaDefinition.ParseSchema
        Dim colnames As String() = schema.columns.Select(Function(c) c.Name).ToArray

        For Each row As Sqlite3Row In rows
            Dim jsObj As New JavaScriptObject

            For i As Integer = 0 To colnames.Length - 1
                jsObj(colnames(i)) = row.Item(i)
            Next

            Yield jsObj
        Next
    End Function
End Class

如何加载数据源驱动程序

数据源驱动程序是通过脚本最开始的imports命令操作来完成的。在LINQ查询脚本的解析操作过程之中,解析器会将imports操作附加到LINQ表达式对象之中,例如:

imports "Sqlite3"

可以看到,我们通过这段代码将要向LINQ查询脚本的执行环境中加载一个来自于Sqlite3.dll的驱动程序。对于这个驱动程序的加载过程,我将加载过程放在了LINQ查询引擎的注册表之中完成:

Dim assembly As Assembly = Assembly.LoadFile(driverDll)

For Each type As Type In From m As Type
                         In assembly.GetTypes
                         Let flag As DriverFlagAttribute = m.GetCustomAttribute(Of DriverFlagAttribute)
                         Where Not flag Is Nothing
                         Select m

    Dim flag As DriverFlagAttribute = type.GetCustomAttribute(Of DriverFlagAttribute)()

    drivers(flag.type) = Function(args)
                             Return Activator.CreateInstance(type, {args})
                         End Function
Next

上面所示的一个数据驱动程序注册过程实际上就是一个反射加载类型的过程:

  • 我们首先通过Assembly.LoadFile函数从文件路径加载目标驱动程序文件
  • 之后在目标程序集之中查找具有DriverFlagAttribute标签的类型
  • 查找到目标标签数据后,我们就有了类型名称了。这样子我们就拥有了一个从脚本之中所声明的类型名称与相对应的驱动程序的关联关系了

在Registry模块之中,存在有一个哈希表:

ReadOnly drivers As New Dictionary(Of String, IDriverLoader)

这个哈希表就是起着关联类型名称与对应的驱动程序之间的关系的功能。例如,我们在LINQ脚本之中声明了类型:

FROM x as table("GenePathways")

其将会被解析为符号申明:

  • 符号类型为来自于数据源驱动程序table
  • 数据源驱动程序的初始化参数值为GenePathways

通过前面的代码可以了解到,我们所编写的Sqlite数据源驱动程序是通过自定义属性标记为table名字的:

<DriverFlag("table")>

所以,我们就可以通过这样子的类型声明,加载Sqlite数据库之中对应的GenePathways数据表作为我们的LINQ查询操作的数据源了。

在加载数据源驱动程序模块的时候,我们就只需要按照定义的类型名称,在代码中查找哈希表就可以了:

Public Function GetReader(type As String, arguments As String()) As DataSourceDriver
    If type = "row" Then
        Return New DataFrameDriver
    ElseIf drivers.ContainsKey(type) Then
        Return drivers(type)(arguments)
    Else
        Throw New MissingPrimaryKeyException(type)
    End If
End Function

LINQ查询的DEMO

下面是一个用于Sqlite数据源驱动程序的DEMO的LINQ查询脚本:

imports "Sqlite3"

FROM x as table("GenePathways")
IN "xcc.db"
WHERE x.pid = 11
ORDER BY gid

在上面的LINQ查询脚本之中,因为Sqlite并非系统内置的数据源驱动程序,其是定义在外部的一个驱动程序。所以我们会首先需要通过imports命令将我们前面编写好的Sqlite数据源驱动程序加载进入LINQ查询的解释器环境之中。

接着,就是我们的LINQ查询Sqlite数据库的具体查询内容了,在这个DEMO里:

  • 我们打开的是一个文件名叫xcc.db的Sqlite数据库文件
  • 查询的对象是该Sqlite数据库文件里面的一个名称为GenePathways的数据表,从之前的R#脚本查看可以看到,在这个数据表中存在有gid和pid这两个字段。
  • 我们对这个数据表应用筛选条件:pid的值应该是等于11的
  • 之后输出表格的筛选结果,并且结果应该是按照gid升序排序的

LINQ脚本的命令行传参

在LINQ脚本之中,可以通过?"argument_name"的方式从命令行之中得到参数信息,例如我们可以将上面的脚本改写为下面从命令行参数中得到数据库文件位置:

Imports "Sqlite3"

# LINQ "./test/sqlite_cli.linq" --table "GenePathways" --db "./test/xcc.db"

FROM x as table(?"--table")
IN ?"--db"
WHERE x.pid = 11
ORDER BY gid
TAKE 25

在VisualStudio之中调试一下上面的脚本,我们的解释器程序已经正确的从命令行参数--table之中得到"GenePathways"这个数据表名称了。

好的,我们将上面的demo查询脚本保存一下,通过LINQ命令执行一下。我们可以看到,LINQ查询解释器已经将所有的pid等于11的结果都筛选出来了:

嗯,结果的表格有点太长了,我们应用一下TAKE操作,只取前25条记录看看。好的,LINQ查询执行成功,没有任何问题了。

Attachments

One response

Leave a Reply

Your email address will not be published.