https://github.com/xieguigang/Darwinism

LINQ(Language Integrated Query)技术是一种语言集成查询,即LINQ是VisualBasic语言之中的一种语法。其由微软公司于.NET Framework 3.5引入的一种SQL查询语言非常相似的数据查询语法。

LINQ与SQL之间的比较

LINQ的语法与SQL非常的相似。只不过LINQ是执行在应用程序代码中,而SQL的执行则是一般与程序代码隔离开,执行在数据库服务器上。并且LINQ的数据源对象不仅仅是表格,任何.NET之中的集合类型都可以作为LINQ的数据源;而SQL则只能够作用于二维的数据表之上。

下面展示的是一段VisualBasic程序中的LINQ查询语句

' https://github.com/xieguigang/sciBASIC/blob/f665331bedf4811042de26fa881f73d9ea503867/Data/DataFrame/StorageProvider/Reflection/StorageProviders/MetaAttributes.vb#L59

From prop As PropertyInfo
In type.GetProperties(BindingFlags.Public Or BindingFlags.Instance)
Let attrs As Object() = prop.GetCustomAttributes(attrEntry, inherit:=True)
Where Not attrs.IsNullOrEmpty
Let mattr As MetaAttribute = DirectCast(attrs.First, MetaAttribute)
Select New ComponentModels.MetaAttribute(mattr, prop)

可以看到,LINQ查询语句于SQL查询语句的语法非常的相似,除了SELECT关键词的位置有点不一样外,其他的都没有太多差别。

LINQ查询脚本引擎开发

自己在平时的工作之中,会需要经常操作Excel表格进行一些数据操作。进行Excel表格操作,比较方便的一种方式就是使用R/R#语言之中的数据框对象。另外的方法就是自己手动在Excel表格里面操作数据了。但是因为在R语言之中对表格的数据筛选语法有点复杂(R中的数据框是向量化的,并且字符串经常是处于Factor状态中,需要通过字符串匹配筛选,在R脚本中会有点麻烦);Excel之中的数据筛选,因为要使用鼠标点点点,批量化操作就更加麻烦了。

所以我在这里开发了一个LINQ脚本化查询的计算引擎,用来方便自己平时的Excel数据操作。项目的源代码放在了这个位置: https://github.com/xieguigang/Darwinism/tree/master/LINQ/LINQ

在LINQ查询脚本引擎项目中,查询引擎是由三部分组成的:

  • Interpreter文件夹中的源代码是于脚本语法解析相关,以及脚本执行相关的模块
  • Language文件夹中的则放置了怎样从文本中解析出查询表达式的单词的解析工具代码
  • Runtime文件夹中则定义了LINQ查询脚本的运行时环境,例如内置的数学函数的支持,LINQ查询所需要的数据源驱动程序的支持等

下面的一段代码来自于Program.vb文件,它展示了这个LINQ数据查询引擎是如何进行工作的:

Dim tokens = LINQ.Language.GetTokens(file.ReadAllText).ToArray
Dim query As ProjectionExpression = tokens.PopulateQueryExpression
Dim env As New GlobalEnvironment(New Registry)
Dim result As JavaScriptObject() = query.Exec(New ExecutableContext With {.env = env, .throwError = True})
Dim table As DataFrame = result.CreateTableDataSet
Dim text As String()() = table _
    .csv _
    .Select(Function(c) c.ToArray) _
    .ToArray

Call text.PrintTable
  • 首先我们需要正确的从文本字符串之中解析出语句的单词,得到一个tokens列表
  • 之后,就可以在这个单词的基础上构建出查询表达式
  • 再然后,我们就可以创建一个查询引擎对象,执行我们的查询表达式
  • 最后通过查询表达式得到结果之后,我们将结果以表格的形式打印出来

上面的代码描述的就是这个查询引擎的大致的工作流程了。下面,我们开始从LINQ查询脚本执行的最开始阶段:从文本中解析出LINQ查询单词来学习怎样创建出一个自己的查询引擎吧。

1. LINQ Language

在构建出解释器中的查询表达式对象之前,我们首先需要从脚本文本之中正确的解析出单词。在这个LINQ查询脚本引擎中,我主要是定义了下面的几种单词

Public Enum Tokens
    Invalid
    keyword

    Open
    Close

    [Operator]

    Symbol
    Reference

    ''' <summary>
    ''' the string interpolation
    ''' </summary>
    Interpolation
    ''' <summary>
    ''' the string literal
    ''' </summary>
    Literal

    Number
    [Integer]
    [Boolean]

    Comma
    ''' <summary>
    ''' 与VB语言类似的,是以换行作为语句结束
    ''' </summary>
    Terminator

    Comment
End Enum

单词的解析这一块是非常简单的一个步骤。我们在定义了上面列举出来的单词类型之后,在Tokenizer模块之中对单词按照模式规则取出来就可以了。

Public Iterator Function GetTokens() As IEnumerable(Of Token)
    Dim token As New Value(Of Token)

    Do While text
        If Not token = walkChar(++text) Is Nothing Then
            If buffer <> 0 Then
                Yield createToken(Nothing)
            End If

            Yield token
        End If
    Loop

    If buffer > 0 Then
        Yield createToken(Nothing)
    End If
End Function

2. 创建表达式

在聊在这个引擎中所创建的表达式之前,我们先来了解一下在这个引擎中的所有的表达式的基础类型

''' <summary>
''' the Linq expression
''' </summary>
Public MustInherit Class Expression

    Public ReadOnly Property name As String
        Get
            Return MyClass.GetType.Name.ToLower
        End Get
    End Property

    ''' <summary>
    ''' Evaluate the expression
    ''' </summary>
    ''' <param name="context"></param>
    ''' <returns></returns>
    Public MustOverride Function Exec(context As ExecutableContext) As Object

End Class

在表达式基础类型之中,其仅具有一个name属性,用来方便进行调试查看;以及一个Exec方法,这个Exec方法可以接受一个运行时环境参数用来在表达式之间进行数据传递从而完成查询操作的从上往下的一个执行过程。

在LINQ查询脚本引擎中,我初步划分了下面的一些表达式类型:

2.1 元表达式

元表达式就是一般意义上的编程语言之中的表达式。在这个查询引擎之中,元表达式是以VisualBasic的语法为基础而解析的。也就是说,你几乎可以将VisualBasic源代码之中的LINQ查询表达式原封不动的复制出来,在这个查询引擎中执行。

  • SymbolReference 符号引用表达式与常量表达式构成了整个表达式求值的最基本的元素。这个表达式就是表示用来存储数据的一个变量。这个表达式主要是调用的Environment.FindSymbol这个函数来进行变量值的查询或者设置。
  • MemberReference 对象成员引用,则是实现类似于从a对象中取出b成员的值的过程,例如实现语法a.b。这个操作过程可以是用于赋值操作,也可以是用于取值操作。不过在这个LINQ查询表达式之中,这个表达式一般是用于取值操作(因为表达式的作用就是产生值)。
  • Literals 常量表达式的作用就是将字符串翻译为对应的字符串,数值或者逻辑值等常量值
  • FuncEval 函数调用表达式,是用于实现f(x)之类的函数求值的操作过程
  • BinaryExpression 二元操作符表达式,则是用于实现诸如a+b之类的二元运算
  • ArrayExpression 向量表达式就是一种用于创建出数组的表达式。创建数组的时候,里面的元素可以是常量,也可以是其他类型的表达式。

以上的这些列举出来的元表达式,他们构成了LINQ查询表达式的基础。

2.2 查询表达式

2.2.a 关键词表达式
  • WhereFilter 这个表达式就是实现WHERE <boolean>这样子的一个逻辑筛选的功能
  • SymbolDeclare 这个表达式主要是用于实现一个变量申明的过程。因为对于一条LINQ表达式而言,其就是相当于一段小小的函数。这条LET x as type = <value>语句,就是用于实现这个函数内部的局部变量的申明操作
  • OutputProjection 这个表达式仅仅是在投影查询表达式之中有效,其作用就是用于生成新的表格结构。这个表达式可以被忽略,当这个表达式缺省的时候,默认输出的结果为原表格的表格结构。这个表达式实现了SELECT ...
  • OrderBy 对新表格中的结果按照某一列数据进行升序排序或者降序排序操作,实现ORDER BY ...
  • SkipItems 跳过新表格的前n条记录,实现SKIP n
  • TakeItems 只取出新表格之中的前n条记录,实现TAKE n

在输出表格投影的结果的时候,SELECT投影表达式,主要是依靠字段列表产生新的对象的方法来产生新的表格投影,这个投影的实现过程如下所示:

Public Overrides Function Exec(context As ExecutableContext) As Object
    Dim obj As New JavaScriptObject

    For Each field In fields
        obj(field.Name) = field.Value.Exec(context)
    Next

    Return obj
End Function

对于ORDER BY,SKIP,TAKE关键词而言,由于其发生在投影操作结束之后,执行的是对整个新的表格之中的数据行对象的整体操作。所以我为这三个表格是专门创建了一个集合操作的管道函数用来执行集合操作。

Public Function Exec(result As IEnumerable(Of JavaScriptObject), context As ExecutableContext) As IEnumerable(Of JavaScriptObject)

我们在投影查询表达式执行的末尾,通过调用一个流程模块按照脚本中定义的顺序执行上面的三种关键词操作就可以达到所期望的诸如结果排序,结果取子集等操作了:

Public Class Options

    Dim pipeline As PipelineKeyword()

    Sub New(pipeline As IEnumerable(Of Expression))
        Me.pipeline = pipeline _
            .Select(Function(l) DirectCast(l, PipelineKeyword)) _
            .ToArray
    End Sub

    Public Function RunOptionPipeline(output As IEnumerable(Of JavaScriptObject), context As ExecutableContext) As IEnumerable(Of JavaScriptObject)
        Dim raw As JavaScriptObject() = output.ToArray
        Dim allNames As String() = raw(Scan0).GetNames
        Dim env As Environment = context.env

        For Each name As String In allNames
            If Not env.HasSymbol(name) Then
                Call env.AddSymbol(name, "any")
            End If
        Next

        For Each line As PipelineKeyword In pipeline
            raw = line.Exec(raw, context).ToArray
        Next

        Return raw
    End Function
End Class
2.2.b LINQ表达式
  • ProjectionExpression 投影查询表达式主要是执行一个表格查询,之后生成一个新的表格结果。
  • AggregateExpression 聚合查询表达式主要是执行一个表格查询,之后只产生一个结果值

3. 查询的数据源

对于一个查询计算引擎而言,其各项操作都是基于一个数据源来完成的。对于SQL查询脚本而言,其数据源非常简单,就是来自于一个数据表。但是对于这个LINQ数据查询引擎而言,其数据来源相比较于SQL查询脚本,更加的多样化。我对这个LINQ数据查询引擎的设想是不仅仅用于处理Excel表格数据,而是可以通过插件拓展到不同的数据源之中。例如

  • Sqlite引擎集合在一起,可以通过这个LINQ查询引擎进行Sqlite数据库查询操作
  • GraphQuery引擎结合在一起,可以通过这个LINQ查询引擎结合GraphQuery脚本直接进行网页数据的查询分析操作
  • CDF文件引擎结合在一起,就可以通过这个LINQ查询引擎直接进行一些科学数据的查询分析操作

所以为了实现这种可拓展性的操作,我专门设计了一个数据源驱动程序模块。我们只需要对新的数据源定义对应的数据驱动程序插件模块,就可以直接使用这个LINQ查询引擎执行数据查询操作。

因为这个LINQ脚本数据查询引擎最初是为了EXCEL表格之中的数据查询操作而设计的,所以在这个引擎之中,默认添加了对CSV文件的支持。我们可以看到,我们只需要继承了DataSourceDriver数据驱动程序模块这个抽象类型;并实现了驱动程序模块对应的数据源读取方法,就可以直接将我们所实现的数据源驱动程序加载进入LINQ查询引擎中,实现对应的文件数据的查询分析操作。对于默认的CSV文件的数据源读取,实现的代码如下:

Public Class DataFrameDriver : Inherits DataSourceDriver

    Public Overrides Iterator Function ReadFromUri(uri As String) As IEnumerable(Of Object)
        Dim dataframe As DataFrame = DataFrame.Load(uri).MeasureTypeSchema
        Dim obj As JavaScriptObject
        Dim headers As String() = dataframe.HeadTitles

        For Each row As Object() In dataframe.EnumerateRowObjects
            obj = New JavaScriptObject

            For i As Integer = 0 To headers.Length - 1
                obj(headers(i)) = row(i)
            Next

            Yield obj
        Next
    End Function
End Class

使用LINQ查询Excel文件

使用LINQ脚本进行Excel表格的查询非常的方便。例如我们将下面的一段LINQ查询代码保存为文件之后,通过LINQ脚本解释器命令执行我们所保存的查询文件:

FROM x AS row
IN "./data.csv"
WHERE x.PeakQuality >= 0.999
SELECT x.LipidIon,
       lipidName = x.Class & x.FattyAcid,
       x.Class,
       x.FattyAcid,
       x.Ion,
       x.Formula,
       x.PeakQuality,
       x."m-Score"

SKIP 100
ORDER BY pow("m-Score", PeakQuality) DESCENDING
TAKE 15

上面的LINQ查询脚本的含义是:

  • 数据源是来自于一个文件路径为./data.csv的Excel表格文件
  • 筛选条件为PeakQuality字段的值应该是大于或者等于0.999的
  • 之后对表格的筛选结果进行一些字段的投影操作,输出指定格式的表格结果
  • 并且结果应该是跳过前100条记录后,按照两个字段:"m-Score" ^ PeakQuality的幂指数计算结果降序排序后的前15条记录

可以看到,查询脚本已经正确的按照筛选条件和输出选项为我们输出了正确的表格筛选操作的结果内容。

Attachments

2 Responses

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

Leave a Reply to 【Darwinism】LINQ脚本查询NetCDF数据集 – この中二病に爆焔を! Cancel reply

Your email address will not be published.

博客文章
May 2022
S M T W T F S
1234567
891011121314
15161718192021
22232425262728
293031