技术细节

  • 个人的一些脚本和代码,经常会分散在不同的地方,管理起来并不方便,例如给WordPress编写的Plugin、测试MySQL时使用的一些脚本等,所以打算全部使用GitHub管理起来。对于个人使用,GitHub提供了私人仓库以存储代码,可以较为方便的管理一些还没有公开的个人代码。

    建立个人Git和GitHub环境

    GitHub CLI是一个具体简单交互式操作的命令行,可以完成与GitHub相关的一些交互与操作。对应的软件包/命令是gh

    安装gh-cli

    参考:Installing gh on Linux and BSD。Amazon Linux 2上安装:

    sudo yum-config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
    sudo yum install gh

    使用gh配置GitHub授权

    接着,就可以使用gh auth login命令来进行GitHub的认证了(gh cli manual)。这是一个简单的交互式命令,这里使用https+token的方式完成认证(也可以使用浏览器的方式辅助完成命令行认证):

    gh auth login
    ? What account do you want to log into? GitHub.com
    ? What is your preferred protocol for Git operations on this host? HTTPS
    ? Authenticate Git with your GitHub credentials? Yes
    ? How would you like to authenticate GitHub CLI? Paste an authentication token
    Tip: you can generate a Personal Access Token here https://github.com/settings/tokens
    The minimum required scopes are 'repo', 'read:org', 'workflow'.
    ? Paste your authentication token: *********************************************************************************************
    - gh config set -h github.com git_protocol https
    ✓ Configured git protocol
    ! Authentication credentials saved in plain text
    ✓ Logged in as orczhou

    关于Token的配置与获取,可以参考:GitHub->Settings->Developer Settings ,这里不再详述。注意,Token意味着分配的所有的仓库权限,必须妥善保管,否则可能会带来巨大的安全隐患。

    如果要登出的话,则可以简单的使用如下命令:

    gh auth logout

    在本地pull与push仓库

    • 首先,在git中配置本地身份(用户名与)
    git config --global user.name "orczhou"
    git config --global user.email "orczhou@orczhou"
    • 首先,新建一个本地模板,并使用git命令初始化
    mkdir  terraform && cd terraform
    git init
    • 配置远端(remote)分支;并拉取远端代码
    git remote add origin https://github.com/orczhou/cloud-mysql-benchmark.git
    git pull origin main

    向远端push代码

    这时,如果修改了仓库中的代码,则可以使用push命令向远端发起提交请求。

    修改、测试并本地提交代码:

    vi gcp_rds_ins/all_in_one/README.md
    git add gcp_rds_ins/all_in_one/README.md
    git commit -m "gcp readme updated"

    向远端push修改:

    git push -u origin main

    该操作会向远端仓库的main分支,提交代码。

    向main分之合并代码

    可以在GitHub仓库页面,对比并创建一个pull request。

    发起pr之后,代码仓库则可以进行merge操作,将代码合并到main分之。

    在新增远程代码库(origin)

    git remote add origin https://github.com/orczhou/testing-delete-repo-if-u-like.git

    将本地代码,提交到远程代码库(origin)的main分支:

    git push -u origin main

    上面的,-u origin main ,其中-u参数表示push的目标代码库-u | --set-upstream

    在现有仓库的main分之上开发

    经常需要做这个动作,常用的命令比较简单,这里记录如下:

    mkdir repo_bench && cd repo_bench
    git init
    git branch -M main
    git remote add origin https://...
    git pull origin main
    
    

    直接修改本地main中的代码并提交到源端:

    cat "..." > README.md
    git add README.md
    git commit -m "first commit" --dry-run
    git commit -m "first commit"
    git push -u origin main

    使用gitignore忽略文件

    在代码开发过程中,由于编译、运行等原因会产生很多的中间文件,而这些中间文件时无需提交到代码仓库的。这时候,需要使用gitignore来忽略这部分文件。详细完整的gitignore的使用可以参考man gitignore

    常用的gitignore是可以通过.gitignore文件来实现,即在代码的根目录中新建该文件,那么在代码处理时,就会根据根据该文件的规则进行忽略。例如Terraform脚本常用的gitignore文件可以参考:

    所以,一个Terraform脚本的.gitignore可以依次作参考:

    # Compiled files
    *.tfstate
    *.tfstate.backup
    *.tfstate.lock.info
    
    # Directories
    .terraform/
    .vagrant/
    
    # SSH Keys
    *.pem
    
    # Ignored Terraform files
    *gitignore*.tf

    master分支与main分支

    在搜索git/github的相关资料的时候,经常还会搜索到master分支作为主分支的资料或者仓库。在2020年的George Floyd的案件发生后,美国的Black_Lives_Matter运动达到了前所未有的高度,最终也影响到在计算机领域的master/slave 一词的使用。更多的参考:Renaming the default branch from master@GitHubWhy GitHub renamed its master branch to main@theserverside

    不过,git在本地默认还是使用默认的master分支,所以如果没有手动切换分支,则还是会经常“默认的”创建master分支。

    查看未提交的修改

    git面向的场景就是分布式、多任务的开发代码管理,其独特的”three tree“模型可以很巧妙的实现这些能力。这也给初学者带来了很多理解上的障碍。

    git diffgit diff HEAD

    如果,想要查看自上次commit以来的所有变更,则需要试用git diff HEAD命令,通常HEAD指向的是,最后一次commit时的位置。

    git diff 工作区 vs 暂存区

    git diff –cached 暂存区 vs 版本库

    同步远程更新

    个人代码仓库管理中,有时候会有这样的情况:直接在远程仓库中修改了一些文件,然后如何让本地和远程保持同步。考虑这样的场景:直接在GitHub上对README.md文件进行了编辑,那么本地代码仓库如何保持这个更新。

    当然,这样做,通常可能会很危险:可能会覆盖掉你本地所做的更改,但是基于上面的场景,所以,有时候会需要这么做。Stackoverflow上有几个相关的问题,非常详细的介绍了做法:

    这里的推荐做法是这样,如果本地仓库的修改确定不要了(通常这是很危险的):

    git pull

    如果本地仓库修改都还需要:

    git stash
    git pull 
    git stash pop

    还可以:

    • 先使用 git fetch更新origin/main
    • 然后使用git diff main origin/main查看本地与远程的差异
    • 最后使用git mergeorigin/main与本地合并,并保持在本地

    这样origin/main是最新的,且本地分支也是最新的了

    git fetch
    git diff main origin/main
    git merge

    参考链接

  • 引言

    这是一个系列文章,旨在了解如何使用Flex/Lex和Yacc/Bison进行词法和语法解析。在前面,已经完成了使用Lex/flex做基础的词法解析实现一个简单的计算器。开始这个系列的第三部分,使用flex和bison完成一个更加复杂的的编译程序。整体上有一定的复杂度,所以分上下篇分别介绍。上篇介绍:实现概要、语法、,下篇介绍数据结构与实现(包括解析树实现、执行)。

    • 下篇介绍:数据结构与实现
      • 与Grammar对应的“解析树”
      • “解析树”的执行
      • 解析树的节点,Terminals 和 Non-Terminals
      • 一个“简单语句”的解析树结构
      • 一个“略微复杂语句”的解析树结构

    解析与执行包含赋值、IF、WHILE等语句的程序

    在前面的案例中,我们实现了一个简单的加减乘运算的计算器程序。这里我们尝试实现一个更复杂一些的编译程序,语法能够支持如下内容:

    • 包含了变量,可以对变量赋值,也可以在表达式中使用变量。但是为了简化程序,这里变量仅限于使用单个小写字母,即[a-z]
    • 支持条件运算,这里定义简单的语法如下:if ( expr ) expre ; (忽略了else语法)
    • 支持比较运算符,包括大于、小于
    • 支持循环运算,支持while循环
    • 和前面案例的一样,仅处理整数,故不处理除法,也不考虑整数溢出等边界问题

    我们用这几个能力,可以编写如下的程序:

    i = 1;
    a = 0;
    while ( a < 100 ) {
      i = i + 1;
      a = a + i;
    }
    print i;

    这个程序实现了一个简单的功能,解决的问题是:在自然数序列(1、2、4…)中,前面多少个自然数的和首次大于100。你可以使用上面的命令编写其他的任意程序。

    解析树的节点

    如果只是使用前面的指令,似乎难以实现对if/while语句的支持。这里,就需要使用典型的编译与执行思路了,先使用语法解析构建一个“语法树”(也叫“解析树”),然后再执行该解析树。具体的,一些设计如下:

    • 使用一个全局数组(int * var[26])存储变量,因为在前面限制了变量名只能是[a-z]
    • 每个grammar rule对应一个tree node,并依次构建一棵语法树
    • 语法树的节点设计如下:
    typedef struct t_node{
        enum NODETYPE nt;
        struct t_node* left;
        struct t_node* right;
        int i;  // for NT_INTEGER NT_VAR_NAME node
    }t_node;

    这里为了简化:

    • 所有语法节点都存放在该结构中
    • 对于变量名,本应该是一个字符,这里在存储时,直接使用其ASCII码将其存储为整数

    语法规则设计

    在开始实现与执行解析树之前,我们先定义语法规则,以支持赋值、if、while、print等语法。语法规则定义时,需要注意尽量避免出现shift/reduce冲突,并且这里的语法规则不包含Action部分:

    //start symbol
    program: 
            | program statement_block   { printf("\n job done! \n"); }
    ;
    
    statement_block: 
            | statement_block statement
    ;
    
    statement: assignment
            | print_func
            | if_block
            | while_block 
    ;
    
    while_block: WHILE '('  bool_expr ')' '{' statement_block '}'
    
    if_block: IF '(' bool_expr ')' '{' statement_block '}'
    
    assignment: VAR_NAME '=' expression ';'
    
    print_func : PRINT expression ';'  
            | PRINT VAR_NAME ';'
    
    bool_expr: expression GT expression
            |  expression LT expression
    
    expression: INTEGER
            | VAR_NAME
            | expression O_ADD expression
            | expression O_MINUS expression
            | expression O_MULTIPLY expression

    节点分析

    节点分析:INTEGER VAR_NAME
    • 节点类型(enum NODETYPE)分别是:NT_INTEGER NT_VAR_NAME
    • 没有子节点,故left/right node都是NULL
    • 在初始化时,
      • 对于INTEGER:t_node.a 存储的是具体的整数
      • 对于VAR_NAME:则存储的变量名,这了变量名为[a-z],则将其ASCII存放于t_node.a

    节点分析:expression 与 O_ADD O_MINUS O_MULTIPLY

    expression对应的语法规则如下

    expression: INTEGER
            | VAR_NAME
            | expression O_ADD expression
            | expression O_MINUS expression
            | expression O_MULTIPLY expression

    那么,再看看O_ADD O_MINUS O_MULTIPLY这类节点:

    • NODETYPE 分别是 NT_O_ADD NT_O_MINUS NT_O_MULTIPLY
    • 都有两个子节点,left / right
    • 在execute之后存储,各个expression计算的结果值t_node.a
    • 注意,在这个设计中,无需有一个独立的expression节点

    bool表达式(bool_expr),通常用于条件判断

    bool_expr: expression GT expression 
            |  expression LT expression 
    • 节点类型(enum NODETYPE)为 NT_BOOL_EXPR
    • 有两个子节点,执行该节点时,需要执行两个子节点之后,获得两个子节点的结果值,再进行比较
    • 返回值为为bool型,这里使用int存储,0表示FALSE 1表示TRUE

    print_func节点

    这个节点实现一个打印整数值的功能,参数可以是一个变量,也可以是一个表达式:

    print_func : PRINT expression ';'  
            | PRINT VAR_NAME ';' 
    • NODETYPENT_PRINT_FUNC
    • 只有一个子节点,为一个 expression 或 VAR_NAME (注:这里应该只用expression就可以了,因为VAR_NAME也是expression)
    • 执行该节点时,则需要实际调用一次打印函数,向标准输出打印expression的结果值

    assignment 赋值语句

    assignment: VAR_NAME '=' expression ';'

    赋值语句左边是变量名,这里定义是[a-z],右边是一个表达式,语句以分号结束。

    • 其节点类型(NODETYPE)为:NT_ASSIGNMENT
    • 左子节点为 VAR_NAME ,右子节点为 expression
    • 其执行时,需要将expression的结果值,存储到变量数组对应的整型变量中

    while_block WHILE子句

    while_block: WHILE '('  bool_expr ')' '{' statement_block '}'
    • 其节点类型(NODETYPE)为:NT_WHILE
    • 左子节点为: bool_expr 右子节点为 : statement_block
    • 执行该节点时,也是也while循环执行,条件部分是执行并判断bool_expr的真假,再决定是否执行右子节点。这里需要注意,每次获取bool_expr的时候,都需要先执行一次该节点。

    if_block IF子句

    if_block: IF '(' bool_expr ')' '{' statement_block '}'
    • 其节点类型(NODETYPE)为:NT_IF
    • 左子结点为 bool_expr 右子节点 : statement_block
    • 执行时,先执行左子节点,再获取其结果的真/假,再判断是否执行右子节点

    statement

    statement: assignment
            | print_func
            | if_block
            | while_block

    可以看到,statement由assignment、print_func、if_block、while_block是这些中的任何一个。所以,在实际构建中,并不会有该节点。与expression类似。

    statement_block 多个statement

    statement_block: 
            | statement_block statement
    ;
    • 其节点类型(NODETYPE)为:NT_STATEMENT_BLOCK
    • 左子节点为 : statement_block,即为statement_block或者assignment、print_func、if_block、while_block中的任意一个;右子节点为 : assignment、print_func、if_block、while_block
    • 执行时,先执行左子节点,再执行右子节点

    program

    program:
            | program statement_block  { printf("\n job done! \n"); }
    ;

    主要的数据结构与函数

    • build_node:构建当前语法规则的节点,该函数返回当前构建出来节点的指针,通常也是各个语法规则Action部分的返回值。
    t_node* build_node( 
        enum NODETYPE nt,
        t_node* left,
        t_node* right,
        int i)
    • exec_node:执行某个节点,并执行其左/右子节点(如果存在的话)。不同的节点的执行操作会有一些不同,例如:
      • if节点需要做一些判断,再决定是否执行;
      • while节点则需要循环bool_expr以决定是否执行某段代码。
      • 加法节点,则需要执行左右子节点执行结果,并相加

    各类节点的exec操作可以参考上一节的详细描述。该函数定义如下

    int exec_node(t_node *n)
    • 解析树的节点与节点类型
    typedef struct t_node{
        enum NODETYPE nt;
        struct t_node* left;
        struct t_node* right;
        struct t_node* rrnode;
        int i;  // for NT_INTEGER NT_VAR_NAME node
    }t_node;
    enum NODETYPE{
        NT_STATEMENT,
        NT_IF,
        NT_WHILE,
        NT_PROGRAM,
        NT_STATEMENT_BLOCK,
        NT_O_ADD,
        NT_O_MINUS,
        NT_O_MULTIPLY,
        NT_INTEGER,
        NT_VAR_NAME,
        NT_BOOL_EXPR_GT,
        NT_BOOL_EXPR_LT,
        NT_PRINT,
        NT_ASSIGNMENT
    };

    下一篇,我们将基于此完成完整的代码。

    不包含Action代码的语法

    补充完整的语法文件cal.y

    包括:

    • 入口函数
    • NODETYPE 定义
    • 解析树的节点 t_node
    • 声明节点便利函数 exec_node
    • 用于存储变量的数组 int var[26];
    • YYSTYPE (似乎是不需要)
    • 定义lex需要处理的TOKEN
    • 定义运算符优先级、结合律
    %{
    // 入口函数
    
    #include <stdio.h>
    
    int main (){
        yyparse();
        return 0;
    }
    
    enum NODETYPE{
        NT_PROGRAM,
        NT_STATEMENT_BLOCK,
        NT_STATEMENT,
        NT_IF,
        NT_WHILE,
        NT_O_ADD, 
        NT_O_MINUS,
        NT_O_MULTIPLY,
        NT_INTEGER,
        NT_VAR_NAME
    };
    
    typedef struct t_node{
        enum NODETYPEP nt;
        t_node * left;
        t_node * right;
        t_node * rrnode;
        YYSTYPE yval;
    }t_node;
    
    //递归执行整个parser tree
    int exec_node(t_node *n){
        return 0;
    }
    
    int var[26];
    
    %}
    
    // 定义YYSTYPE
    %union {
        int a;  // for integer
        char c; // for var_name
        bool b; // for bool_expr
    }
    
    
    // 定义Token
    %token <c> VAR_NAME 
    %token <a> INTEGER
    %token O_ADD O_MINUS O_MULTIPLY 
    
    %token GT LT
    
    %token WHILE IF
    %token PRINT
    
    // 定义运算符
    %left O_ADD O_MINUS
    %left O_MULTIPLY
    

    补充Lex文件

    %{
        #include "cal.tab.h"
    %}
    %option noyywrap
    %%
    [[:digit:]]+ {
        yylval.a = atoi(yytext);
        return INTEGER;
    }
    
    [a-z] {
        yylval.c = yytext[0];
        return VAR_NAME;
    }
    
    "+" { return O_ADD;};
    "-" { return O_MINUS;};
    "*" { return O_MULTIPLY;};
    
    "while"  {return WHILE;}
    "if"  {return IF;}
    "print"  {return PRINT;}
    
    ">" {return GT;}
    "<" {return LT;}
    
    [();={}]  {return yylval.c = *yytext;}
    
    %%

    生成代码并编译、修改

    lex cal.l 
    bison -d cal.y 
    gcc cal.tab.c lex.yy.c -o a.out
    
    
    bison -W -d cal.y
    
    cal.y:62.8: warning: empty rule without %empty [-Wempty-rule]
     program:
            ^
    cal.y:66.16: warning: empty rule without %empty [-Wempty-rule]
     statement_block:
                    ^
    cal.y: warning: 6 shift/reduce conflicts [-Wconflicts-sr]

    错误1:这里的 PRINT expression ‘;’ PRINT VAR_NAME ‘;’ 是有包含关系,重复的。因为VAR_NAME本身也是一个expression。故修改如下:

    print_func : PRINT expression ';'  
            | PRINT VAR_NAME ';' 
    
    expression: INTEGER
            | VAR_NAME
            | expression O_ADD expression
            | expression O_MINUS expression
            | expression O_MULTIPLY expression

    疑问与思考:左边/右边的表达有什么不同。(注意,左边的表达会报shift/reduce conflict)

    program: 
            | program statement_block 
    ;
    
    statement_block: 
            | statement_block statement
    ;
    
    statement: assignment
            | print_func
            | if_block
            | while_block 
    ;
    program: statement_block 
    ;
    
    
    statement_block:
            | statement_block statement
    ;
    
    statement: assignment 
            | print_func 
            | if_block 
            | while_block 
    ;

    修改后的cal.y文件,仅语法部分,不包含Action

    %%
    program:  statement_block { printf("\n job done! \n");}
    ;
    
    statement_block:
            | statement_block statement
    ;
    
    statement: assignment 
            | print_func 
            | if_block 
            | while_block 
    ;
    
    if_block: IF '(' bool_expr ')' '{' statement_block '}' 
    
    while_block: WHILE '('  bool_expr ')' '{' statement_block '}' 
    
    assignment: VAR_NAME '=' expression ';'
    
    print_func : PRINT expression ';'
    
    bool_expr: expression GT expression
            |  expression LT expression
    
    expression: INTEGER
            | VAR_NAME
            | expression O_ADD expression
            | expression O_MINUS expression
            | expression O_MULTIPLY expression
    
    

  • MySQL在上个月正式EOL,意味着官方不会再发布新版本去修复Bug或者安全漏洞。一般来说,云厂商对老版本支持要更就一些,那么一起来看看各个云厂商对5.7的支持情况。概述:

    • AWS:首先,AWS RDS MySQL将提供额外的、免费的半年标准支持,期间提供bug修复、漏洞修复等;2024年2月后,AWS RDS将提供付费的扩展支持。届时,如果不选择付费扩展支持的实例,将会自动(可以理解为“强制”)被升级到8.0版本,“付费”的扩展支持应该持续3年。(注:Aurora的免费支持时间会更长一些,到2024年11月)
    • 阿里云:目前,对于5.7还没有看到正式的说明。但,对于5.6版本,阿里云数据库曾发通告,将提供额外的三年维护时间。不过鉴于5.7实例体量巨大(因为8.0版本拖的时间太长),对于5.7版本支持的时间可能会更长。
    • 其他云厂商:目前都比较模糊,可以理解为不会有任何动作,用户可以正常使用。

    AWS对MySQL 5.7的支持

    AWS有Aurora MySQL 5.7(即Aurora 2.x系列)、RDS MySQL 5.7两个版本。在Oracle MySQL官方到达EOL后,AWS额外提供了半年(Aurora约一年)的免费标准支持,以及三年的付费支持。即,AWS RDS将免费支持5.7到2024年02月底,Aurora到2024年11月底;另外,还将提供三年的付费支持,约到2027年。是的,要收费的(参考)。这段时间,AWS将继续提供安全相关的Bug修复、以及重要的功能Bug修复。

    这个收费策略应该是开了云厂商对于旧版本数据库支持的先河。维护老的版本,需要额外的技术人员投入,维护一个过期的版本的投入从长时间来看,是低效的,所以,用户是需要为这部分服务付费的。

    AWS 扩展支持的收费方式与费率

    收费方式是按照vCPU * HourACU * Hour计费,不同的区域会有不同,不同的时间也会不同,离官方EOL时间越长,费用会越贵。不过,在官方版本到达EOL之后,AWS预留了4个月~一年的免费时间,让用户安排升级。以RDS MySQL 5.7为例,自2024年2月底之后,以新加坡地区为例,前两年付费扩展支持价格为 $0.12/(vCPU * Hour),第三年的费用为$0.24/(vCPU * Hour)。以一个4c16g(db.m5.xlarge)的实例计算,一个月费扩展支持费用约为$345。而该实例本身的价格约为(两节点高可用):$677 = 0.47*24*30*2。所以,AWS的策略是,价格摆在这里了,升不升级,由用户自己定夺。

    (more…)
  • 引言

    这是一个系列文章,旨在了解如何使用Flex/Lex和Yacc/Bison进行词法和语法解析。在前面,已经完成了使用Lex/flex做基础的词法解析。接着,开始这个系列的第二部分,使用flex和bison完成一个简单的计算器。

    为了简化实现,将注意力放在简单flex和bison使用上,这里对计算器做了几个简化:

    • 只支持加、减、乘计算,暂时不支持除法,除法可能涉及到浮点类型,故暂时忽略
    • 不考虑整数溢出的问题,这里使用int类型(那么他存储与计算范围是有限的)

    也就是该程序可以计算加法、减法、乘法,以及他们的任意组合,如: 3+4、 3+4*2、 3+4*2-3、 3+4*2-3*2

    后续,还将考虑增加更急复杂的计算器,包括:

    • 实现,带有变量的计算器程序
    • 实现带来循环、带有条件判断的程序

    这里先从简单的开始。

    初次手写一个cal.l和cal.y

    这是在vim中写出的第一遍代码,包含了词法文件cal.l和语法文件cal.y,详细如下。这其中当然是有很多错误的,之所以,依旧写出来,是为了和正确代码对比,以此看看对哪些概念理解有偏差。如果你是找一个正确例子的话,可以跳过这一段。

    %{
    #inlcude <stdlib.h>
    %}
    /* 十进制整数 */
    %token INTEGER
    
    %union { 
        int a;
    }
    
    /* 操作符 + - * / */
    %token OPERATOR
    
    %%
    program:
        program expression \n { printf("%d",$2); }   // 这里就是以回车结尾,也可以考虑使用 = 结尾
        |
    
    expression:
    		  INTEGER
    		| expression '+' expression {$$ = $1 + $2}
    		| expression '-' expression {$$ = $1 - $2}
    		| expression '*' expression {$$ = $1 * $2}
    
    dec_integer: 
    		INTEGER
    			{
    			$$ = $1.a;
    			}

    这里也有几个已知的问题,例如:运算符的优先级没有定义,也就说4+3*2可能会算成14。没错,如果眼尖的话,还发现有一些拼写错误。

    接着是cal.l文件:

    #inlcude <stdlib.h>
    #include "y.tab.h"
    ​
    %}
    [:digit:]+ {
                yylval.a = atoi(yytext)
                RETURN INTEGER;
    }

    当然,这里面有很多的错误。一会儿来看后面正确的内容。

    修改cal.l和cal.y

    • 首先,是去解决已知的问题:运算符优先级如何去解决?关于什么是优先级、什么是结合律这里不做详述,这里有一篇文章讲得比较细致,几幅图也非常直观:Precedence and Associativity of Operators in Python。虽然是不同的语言,但意思是一样的。理解这个点还是比较重要的,例如在关系型数据库中,之前有遇到过这样的案例,可以思考一下如下的表达式 t.col < 2 or t.col < 11 and t.col > 4 是什么意思:
    -- 猜测一下,如下的 SELECT 查询是否能够返回记录:
    
    CREATE TABLE t(col INT); 
    
    INSERT INTO t values (1);
    
    WHERE t.col < 2 or  t.col < 11 and  t.col > 4

    扯远了,再回到cal.y文件,优先级和结合律需要进行如下修改,以定义”*”优先级高于”+”、”-“:

    %left '+' '-'
    %left '*'

    这里的代码先后,就定义了优先级;优先级越高,定义在越在后面;left表示,左结合。

    • 除了优先级,在cal.y语法规则中的定义部分,如果有字符,并没有使用引号。例如上面的cal.y的第17行的\n,是需要加上引号的,即 '\n'

    • 对于cal.y的中定义的语法规则,并没有定义返回值存储在联合体(YYSTYPE,也就是如下这里cal.y定义的唯一的那个联合体)哪个类型中。例如,没有定义”expression”这个语法规则,返回值是使用哪个值,虽然这里的联合体只定义了一个类型(即int a)。具体的,添加了如下代码:
    %token <a> INTEGER
    %type <a> expression

    完成这样的定义后,在lex的文件cal.l中,就可以对yylval进行赋值,如:yylval.a = atoi(yytext); 这时,对于yacc文件中cal.y,如果引用这个TOKEN,就可以使用$1(或者是$2、$3)来引用lex解析后的返回值,如:expression: INTEGER { $$ = $1;}

    • 于是,重新使用了独立的Token重新定义了运算符,并定义了优先级,如下:
    cat cal.y
    ...
    %token O_ADD O_MINUS O_MULTIPLY O_EQ
    
    %left O_ADD O_MINUS
    %left O_MULTIPLY 
    
    %token <a> INTEGER
    %type <a> expression
    ...
    
    cat cal.l
    ...
    "=" { return O_EQ;};
    "+" { return O_ADD;};
    "-" { return O_MINUS;};
    "*" { return O_MULTIPLY;};
    ...
    • 没有定义 yyerror 函数,程序也会编译不过去,会报如下错误:
    cal.tab.c:(.text+0x53b): undefined reference to `yyerror'

    按照文档,可以定义一个最简单的yerror函数(参考:The Error Reporting Function yyerror),如下:

    void
    yyerror (char const *s)
    {
      fprintf (stderr, "something error: %s\n over", s);
    }
    • 删除了无效的dec_integer规则,如果有无效的规则,也会失败
    • 将[:digit:]修改为[0-9]+。至于为什么[:digit:]不生效,后面做了一些搜索。为了避免歧义,需要额外再加一层中括号,写法也就是[[:digit:]]。详细参考:Patterns@Lexical Analysis With Flex

    完整的计算器程序文件cal.l cal.y

    补充一个main入口函数,修改cal.l、cal.y即可。

    修正后的cal.l

    %{
        #include "cal.tab.h"
        #include <stdio.h> 
    %}
    %option noyywrap
    %%
    [0-9]+ {
    			yylval.a = atoi(yytext);
    			return INTEGER;
        	   }
    
    "=" { return O_EQ;};
    "+" { return O_ADD;};
    "-" { return O_MINUS;};
    "*" { return O_MULTIPLY;};
    
    %%

    修正后的cal.y

    %{
    	#include <stdlib.h>
    	#include <stdio.h>
    
    
    int main(){
    	yyparse();
    }
    
    void
    yyerror (char const *s)
    {
      fprintf (stderr, "something error: %s\n over", s);
    }
    
    %}
    
    
    %union { 
        int a;
    }
    
    %token O_ADD O_MINUS O_MULTIPLY O_EQ
    %token <a> INTEGER
    
    %left O_ADD O_MINUS
    %left O_MULTIPLY 
    %type <a> expression
    
    %%
    program:
        |
        program expression O_EQ { printf("result is : %d",$2); }   
    ;
    expression:
    		  INTEGER { $$ = $1;}
    		| expression O_ADD expression {$$ = $1 + $3; }
    		| expression O_MINUS expression {$$ = $1 - $3; }
    		| expression O_MULTIPLY expression {$$ = $1 * $3;}
    ;

    编译与执行

    然后,就可以生成c文件并编译可执行文件了:

    lex cal.l 
    bison -d cal.y 
    gcc cal.tab.c lex.yy.c -o a.out
    ./a.out
    4+3*2=
    
    也可以像这样:
    lex cal.l && bison -d cal.y && gcc cal.tab.c lex.yy.c -o a.out && ./a.out

    附带注释说明的cal.y文件

    %{                                    // %{ ... %}  包含了完整的C语言代码        
    	#include <stdlib.h>           // 这里包含需要的头文件、入口函数main
    	#include <stdio.h>            //    以及一个简答的yyerror函数
    
    
    int main(){
    	yyparse();
    }
    
    void
    yyerror (char const *s)
    {
      fprintf (stderr, "something error: %s\n over", s);
    }
    
    %}
    
    
    %union {                             // 这是一个非常重要的联合体,用于定义
        int a;                           // 各种不同类型的Token所返回的数据
    }                                    // 广泛的被yylex使用,并用于与.yy文件共享数据
                                         // 也就是 YYSTYPE 
    
    %token O_ADD O_MINUS O_MULTIPLY O_EQ
    %token <a> INTEGER                   // 这里表示INTEGER(这是一个被lex识别的token)
                                         // INTEGER(被lex识别的token返回值为YYSTYPE.a
    %left O_ADD O_MINUS                  // 这里定义 + -为左结合律
    %left O_MULTIPLY                     // 这里定义 * 为左结合律,并且优先级高于 + -
    %type <a> expression                 // 这里定义语法规则(grammer rule)expression
                                         // 的返回值为 YYSTYPE.a
    %%
    program:                             // 这是start symbol,所有的program都需要符合该定义
        |
        program expression O_EQ { printf("result is : %d",$2); }   
    ;
    expression:
    		  INTEGER { $$ = $1;}
    		| expression O_ADD expression {$$ = $1 + $3; }
    		| expression O_MINUS expression {$$ = $1 - $3; }
    		| expression O_MULTIPLY expression {$$ = $1 * $3;}
    ;

  • 最近,Google Cloud SQL(Google云上的RDS)做了一次大的产品调整与发布:将原来的Cloud SQL分为了两个版本,分别为”Enterprise”和”Enterprise Plus”版本。本文概述了两个版本的异同,以帮助大家更好的了解Google Cloud SQL。

    关于”Enterprise”和”Enterprise Plus”版的Cloud SQL实例

    整体上:

    • Google Cloud SQL相当于AWS或阿里云平台的RDS,Cloud SQL for MySQL就相当于AWS RDS for MySQL
    • 这次调整之后,原来的Cloud SQL for MySQL/PostgreSQL全部变更为Enterprise版本,价格和产品本身没有什么改动,只是名称发生了变化
    • 新增的Enterprise Plus版本提供了更高的产品能力,例如通过硬件/软件优化提供的Data Cache能力,可以提升MySQL的读取和写入性能;Enterprise Plus还提供了更大的规格、更灵活的CPU/内存配比等能力
    • Enterprise Plus资源的价格比Enterprise版本贵约30%,Data Cache空间是额外计费的
    • 本质上,猜测,Enterprise Plus使用了更新更强的机型,具备非常强的本地存储能力(本地可能使用NVMe SSD或者其他存储设备),在数据库层则利用该能力使得用户获得更好的读与写的能力
    • 从产品发布节奏来看,今年GCP(Google云)增加了在Cloud SQL上的投入,这一点与阿里云现在应该是比较类似的。而在以前,在Google数据产品体系中,Spanner、AlloyDB或者BigTable才是投入最大的产品,而Cloud SQL似乎不受重视。现在大概发现,无论Spanner、AlloyDB架构多领先,最简单的Cloud SQL才一直都是更多用户的首选。
    • 这次更名调整,感觉是比较混乱的。对于Cloud SQL for MySQL来说,”Enterprise”和”Enterprise Plus”之间的区别还是比较明显,一个有Data Cache、另一个没有。但对于PostgreSQL来说,两者的区别更像是对资源多少的使用的一种简单限制(最大规格、cpu内存比、PITR的时间限制等)。另外,当前SQL Server又并不区分这两个类型。所以,整体给人感觉比较混乱。而像AWS Aurora新增一个I/O-Optimized选项的做法可能会更简单一点。GCP这种”化简为繁”做法,也许和业务的压力有一定的关系,更像是为了概念而做的产品决策,最终让用户对混乱的产品名称和定位买单,这种事情在大公司的企业级产品中,还是经常出现的。事情做不了什么突破,那么就在产品名称上做一些突破吧(这是一段不负责任的“臆断”,谨慎参考)。
    (more…)
  • 引用80后初中英语课本中的一句话:“how time flies”。是不是还在突然放开后,全家感染的错(meng)愕(bi)中,是的,一年又过去了。在过去的一年中,云数据库技术行业动态一共发布了28期(参考),趁这会儿来做个年度的总结吧。

    云数据库的年度发布

    • Google云发布AlloyDB(参考);火山引擎veDB公测上线
    • 阿里云数据库李飞飞发布全新品牌 “瑶池”,涵括PolarDB、AnalyticDB、Lindorm、DMS等产品,旨在打造云原生一站式数据管理与服务
    • TiDB发布了发布6.0.0版本,TiFlash正式开源,TiDB Cloud正式上线
    • OceanBase发布4.0版本,OceanBase Cloud上线
    • 腾讯云发布KeeWiDB,兼容Redis
    • SQL Server 2022发布,支持Windows和Linux,全面集成Azure云服务
    • Aurora Serverless v2 发布(参考)
    • 华为云GaussDB发布三大架构演进:Serverless、Regionless和Modeless
    • Gartner数据库魔力象限2022发布:阿里领先、腾讯再次进入、华为退出(参考)

    盘点小结:可能很多开发者还没有真正摸清什么是云原生数据库,但是在云厂商一侧,云原生数据库已经是”现在进行时”了。自2014年AWS发布Aurora,2017年阿里云发布PolarDB,几乎所有的云厂商都跟进这一方向,今年,一向”高冷”的Google也发布了AlloyDB,火山引擎发布了veDB。华为云则有自己的GaussDB系列,腾讯云则是TDSQL系列。

    今年,Aurora和阿里云RDS/PolarDB则又在云原生上再向前走了一大步,将数据库的Serverlss弹性能力提升到秒级别(参考)。

    阿里云今年也则在数据库产品平台层面做了一个大动作,发布了全新的”瑶池”品牌,涵括PolarDB、AnalyticDB、Lindorm、DMS等产品,旨在打造云原生一站式数据管理与服务。

    各个厂商都在All in Serverless

    在这个充满变化的时代,数据库的Serverless是明确的。

    • AWS发布Opensearch Serverless之后,实现了整个分析类全线产品Serverless
    • AWS上线了“Neptune Serverless”
    • 阿里云RDS/PolarDB发布Serverless实现秒级弹性;几乎整个瑶池体系的数据库均在All In Serverless
    • PingCAP 推出 TiDB Cloud Serverless Tier BETA 版
    • TDSQL-C 的Serverless能力实现秒级”启停”
    • 中国信通院云大所联合阿里云发布《Serverless数据库技术研究报告》:详情

    盘点小结:在AWS推出Lambda之后,就在全面的All In Serverless,整个基础服务从底层架构、基础计费、产品形态都在向Serverless发展。相比国内Serverless,AWS则有两层含义,一方面是产品规格与计费实现细粒度的按量付费;另一方面,则是全面的与AWS Lamdba等无服务产品的集成,面向用户提供一个完整的无服务的云计算服务。这一点,在阿里云近期的一篇文章中也有介绍,感兴趣的可以去阅读:关系型数据库与Serverless。国内大多数厂商的Serverless都更多的是偏向于实现前者,也就是更细粒度的变配与计费。今年,阿里云RDS在推出Serverless同时,也很快推出了Data API的能力。算是,走到了Serverless的第二层了,具体的可以参考这篇文章:云数据库RDS MySQL Serverless已来

    不过,无奈,据了解,阿里云的”函数计算”使用率应该极低,与Lamdba在AWS的地位完全不可同日而语,所以,这里的Data API能够给用户提供的价值也是受限的,只能算是一个面向未来的布局吧。

    开源:比看起来更困难

    • 6月,Apache IoTDB(Timecho)完成近亿元天使轮融资,专注于IoT领域时序数据存储:参考
    • 7月,开源一体化实时 HTAP 数据库 StoneDB正式发布:参考 参考
    • 8月,蚂蚁集团时序数据库CeresDB正式开源:参考
    • 9月,华为云正式开放时序数据库openGemini源代码,参考
    • 9月,火山引擎也计划将ByConity中部分模块开源:参考

    盘点小结:在全球范围内,开源数据库在市场上占领着重要位置,也有非常多商业和市场成功的案例(MongoDB、ClickHouse等)。在国内,TiDB也凭借开源,在分布式关系型数据库领域取得比较高的市场知名度。在今年,也有部分大厂的数据库选择了开源,也有部分新的开源项目。值得一提的是,在之前StarRocks(原DorisDB)就是基于Apache Doris,今年SelectDB也基于Doris推出,如果围绕开源发展,其早期就要做好知识产权与品牌的管理,否则在产品发展到一定程度的时候,比较容易出问题。

    近期,MariaDB 通过SPAC模式在美上市。上市后,市场预期情况并不理想,连续三个交易股价持续下跌。MySQL构筑的产品、市场、生态以及开发者心智已经非常强大,MariaDB想在这个方向再取得更大的战果,其实并不容易。

    开源有多重含义,其中一重则是”市场”,通过开源培育市场,让初阶开发者、小的业务场景免费试用,然后通过原厂云服务降低用户使用成本、又或是通过企业级能力再进行盈利,从而形成商业的闭环。总的来看,这条路并不很容易。

    2022中国数据库年度人物

    (注:仅代表本文作者观点)

    • 阿里云数据库负责人李飞飞
    • OceanBase创始人阳振坤
    • 达梦数据库创始人冯裕才

    没什么小结的,只有敬佩,他们已经/正在/将要影响中国数据库技术的发展。

    虽然是寒冬,但数据库领域投资火热

    虽然,在今年整个行业大环境遇冷,尤其是下半年,各个大厂纷纷裁员并收紧支出。但从投融资来看,基础软件投资与研发依旧非常火热,在今年的数据库项目方向,处于早期的项目融资就接近20亿。另外,据了解,今年还有很多项目有非常大额的融资,只是还没有公开。

    整体来看,2022年可以说是一个中国数据基础软件创业的小高潮。

    2020年snowflake在美国IPO,2021年Databricks融资估值达380亿美元。以这两家公司为代表的基础技术企业融资,可以说点燃了资本和技术创业的火苗,在2022年,虽然整体投资是在收缩,资本一定程度上,依旧延续了去年的势头,在基础领域资本依旧在增加下注,相比之前,只是更加谨慎了。而过去两年,消费市场投资虽然低迷了很多,但之前消费市场成长起来的大型企业,其背后的基础技术和团队都已经非常强大,这也是这波浪潮背后,除了国家基础设施被重视之外的另一个重要原因。

    还可以看到,面向特定场景(图、时序、向量等)的数据库在兴起。除了经典的OLTP数据库以及分析方向,诸如图数据库NebulaGraph、时序数据库Timecho、向量数据库Milvus也在持续”发育”。

    投融资的热闹,离产品的成功还有非常…非常(非常多的非常)远的距离。产品最终需要获得市场和用户的检验,非常期待这批初创企业,在未来能够成长出更多的参天大树:

    • 4月,实时数仓SelectDB完成超3亿元融资(天使和天使+轮)
    • 6月,DataStax获1.15亿美元投资,开始关注中国市场
    • 6月,开源时序数据库Timecho完成近亿元天使轮融资
    • 7月,达梦数据库科创板上市申请被受理
    • 8月,MariaDB收购地理信息技术提供商“CubeWerx”。
    • 8月,向量数据库公司 Zilliz 宣布完成 B+ 轮 6000 万美元融资
    • 8月,专注于云数据平台的大数据服务商「数新网络」完成数千万元Pre-A轮融资
    • 8月,YAOBASE云尧科技完成千万元种子轮融资,多位业内大咖作为天使投资人参与本轮融资
    • 9月,NebulaGraph获得获得数千万美元的A轮融资
    • 9月,「格睿云Greptime」完成数百万美元天使轮融资
    • 9月,「开云集致」完成千万元级别的Pre-A轮融资
    • 10月,国产数据库公司瀚高获浪潮数亿元战略投资
    • 10月,南大通用宣布完成数亿元新一轮融资
    • 10月,MonoGraphDB完成数千万元天使轮融资
    • 10月,星环科技成功登陆科创板
    • 11月,DuckDB 融资4750万美元
    • 11月,EdgeDB 宣布完成 1500 万美元A轮融资
    • 12月,达梦数据库科创板上市获批
    • 12月,MariaDB 通过SPAC模式在美上市

    “脸谱化”的云数据库厂商

    一直以来,文章风格都很严肃,这次轻松一点。”脸谱化”本来大多数时候是贬义词,不过这里是希望通过拟人的方式看看各个云数据库厂商的情况:如果各个云数据库厂商是一个班的同学,那么会是怎样?

    • 先说说领头羊AWS数据库。AWS像是班上的班长,成绩优秀,学习刻苦,为人诚恳,虽然已经每次考试都名列前茅,但是依旧非常努力。毕竟,后面的第二、三名各个都虎视眈眈。
    • Azure是一个回头浪子,原来对开源很抵触,云计算的起步也比较晚,但是非常有天赋,加上底子其实还是非常不错的,现在又极其努力,所以已经成为了班上的前几名。
    • GCP是一个走竞赛路线的极客,学习成绩一直一般,但是在“开源”“和“技术深度”上都非常强,一直希望通过“开源”、“数据分析”等技术能够保送到理想大学,超过前面同学。
    • Oracle像是一个数学竞赛特长生,在单个领域极其强,现在Oracle想要成为一个各个科目(云计算)都非常强的人,要和所有玩家一起参加全国统一高考,所以现在在全力的补课。不过,现在他的物理、化学(IaaS等服务)等学科落后比较多,追赶起来很吃力。
    • 阿里云是班上的学习委员,虽然出生在遥远的东方,但是学习一直非常刻苦,也很执着,加上天赋还可以,目前,也已经成为班级的前几名。
    • 火山引擎则是一个转学来的插班生,之前在机器学习、视频、分析技术上非常强,现在也进入了云计算领域,还在奋力追赶班上其他同学。
    • 华为云则是一个中等生,家境虽然殷实,但是危机不断。自己则重点发展openGauss和GaussDB和这两个方向。这个同学一直非常努力,也拿了很多的“国家奖学金”,不过因为家庭的其他事情很闹心,学习也时常分心。
    • 诸如人民云等国资云都是属于家里有矿的大户人家,也有着完全不一样的使命和目标,不过也因为身份特殊,不一定什么时候就回家继承家族产业去了。
    • 腾讯云则勉强算是班上的优等生,并在最近一次模拟考试中取得了不错的成绩。

    不过,总的来说,云计算所在的学校,应该算是一个贵族学校。早期就需要非常大的投入,而且投入周期也很长,如果不是家里有矿的,则很难支撑。不过,目前,云计算的价值和其战略意义已经早就没有人质疑了,所以到底谁能够站到最后,还未可知。

    其他

    ARM vs x86:国内和国外云厂商都已经开始在加注ARM,不过两者的原因略有一些不同。AWS发布了新的Graviton3、Azure推出AMD实例、阿里云也发布了基于ARM的架构的RDS MySQL、PostgreSQL。在海外,推出ARM芯片核心在于从性价比角度去解决用户问题,而国内,除此之外,还在尝试去解决芯片依赖的问题。

    多云:“Oracle的Larry和微软Azure的Satya宣布将进一步推进两家公司在多云战略上的合作”。多云已经是用户的必然选择,云厂商之间也在通过一些合作,增加用户使用多云的便利性。

    PostgreSQL在经历一场持续的崛起:今年GCP的AlloyDB发布选择了优先支持PostgreSQL、Azure Cosmos DB也支持PostgreSQL、Google Spanner也提供了PostgreSQL接口、StackOverflow的调研等都明确显示了PostgreSQL正在经历一场慢热的崛起,大家可以考虑投入更多的关注。好了,就做这些小结吧。

    祝大家自己和家人都快点从新冠中恢复过来,迎接全新的2023吧。