Hive Analyzier to Implement Private Data Protection

2 篇文章 0 订阅

目标:

给定一查询SQL.
1. 如果隐私字段出现在最终的查询结果中,不论中间经过多少次别名变换,仍然能够识别。
2. 如果隐私字段出现在最终的查询结果中,中间经过的所有处理函数,都记录下来。
3. 如果隐私字段仅出现在中间查询中,或者仅用隐私字段作为关联条件(如用手机号判断两个商城的重合度),则允许。
一句话:出现在最终结果的查询字段,如果是隐私字段,则判断处理函数是否允许,如果没有处理函数,或者函数不允许,则抛出异常。

数据字典

要定义表的哪些字段是隐私字段,我们需要有数据字典,给定一个SQL,我们从数据字典中判断是否有隐私字段,和判断隐私字段上的函数是否允许。
通过MetaManager.getTable(String dbName, String tableName)得到SQL中Table的定义。
ITable是一个存储根据查询的表名从数据字段中得到的表的定义, 定义了一下方法:

Interface ITable {
    String getDBName(); //返回数据库名
    String getTableName(); // 返回表名
    List<IField> getFieldList(); // 返回字段列表
}

IField的定义如下:

Interface IField {
   String getName(); // 返回字段名
   Boolean canBeDirectlySelect(); // 返回该字段是否可以直接选择
// 如果该字段不可以直接选择,是否可以使用参数functionName对应的函数。
   Boolean canBeInFunction(String functionName); 
}

词法分析

HiveParser.g

HiveParser.g has 2395 lines and import 5 other anltr file.

HiveLexer.g

KW_HOUR: 'HOUR';
KW_MINUTE: 'MINUTE';
KW_SECOND: 'SECOND';

// Operators
// NOTE: if you add a new function/operator, add it to sysFuncNames so that describe function _FUNC_ will work.

DOT : '.'; // generated as a part of Number rule
COLON : ':' ;
COMMA : ',' ;
SEMICOLON : ';' ;

LPAREN : '(' ;
RPAREN : ')' ;
LSQUARE : '[' ;
RSQUARE : ']' ;
LCURLY : '{';
RCURLY : '}';

EQUAL : '=' | '==';
EQUAL_NS : '<=>';
NOTEQUAL : '<>' | '!=';
LESSTHANOREQUALTO : '<=';
LESSTHAN : '<';
GREATERTHANOREQUALTO : '>=';
GREATERTHAN : '>';

DIVIDE : '/';
PLUS : '+';
MINUS : '-';
STAR : '*';
MOD : '%';
DIV : 'DIV';

AMPERSAND : '&';
TILDE : '~';
BITWISEOR : '|';
BITWISEXOR : '^';
QUESTION : '?';
DOLLAR : '$';

// LITERALS
fragment
Letter
    : 'a'..'z' | 'A'..'Z'
    ;

fragment
HexDigit
    : 'a'..'f' | 'A'..'F'
    ;

fragment
Digit
    :
    '0'..'9'
    ;

fragment
Exponent
    :
    ('e' | 'E') ( PLUS|MINUS )? (Digit)+
    ;

fragment
RegexComponent
    : 'a'..'z' | 'A'..'Z' | '0'..'9' | '_'
    | PLUS | STAR | QUESTION | MINUS | DOT
    | LPAREN | RPAREN | LSQUARE | RSQUARE | LCURLY | RCURLY
    | BITWISEXOR | BITWISEOR | DOLLAR
    ;

StringLiteral
    :
    ( '\'' ( ~('\''|'\\') | ('\\' .) )* '\''
    | '\"' ( ~('\"'|'\\') | ('\\' .) )* '\"'
    )+
    ;

CharSetLiteral
    :
    StringLiteral
    | '0' 'X' (HexDigit|Digit)+
    ;

BigintLiteral
    :
    (Digit)+ 'L'
    ;

SmallintLiteral
    :
    (Digit)+ 'S'
    ;

TinyintLiteral
    :
    (Digit)+ 'Y'
    ;

DecimalLiteral
    :
    Number 'B' 'D'
    ;

ByteLengthLiteral
    :
    (Digit)+ ('b' | 'B' | 'k' | 'K' | 'm' | 'M' | 'g' | 'G')
    ;

Number
    :
    (Digit)+ ( DOT (Digit)* (Exponent)? | Exponent)?
    ;

/*
An Identifier can be:
- tableName
- columnName
- select expr alias
- lateral view aliases
- database name
- view name
- subquery alias
- function name
- ptf argument identifier
- index name
- property name for: db,tbl,partition...
- fileFormat
- role name
- privilege name
- principal name
- macro name
- hint name
- window name
*/    
Identifier
    :
    (Letter | Digit) (Letter | Digit | '_')*
    | {allowQuotedId()}? QuotedIdentifier  /* though at the language level we allow all Identifiers to be QuotedIdentifiers; 
                                              at the API level only columns are allowed to be of this form */
    | '`' RegexComponent+ '`'
    ;

fragment    
QuotedIdentifier 
    :
    '`'  ( '``' | ~('`') )* '`' { setText(getText().substring(1, getText().length() -1 ).replaceAll("``", "`")); }
    ;

CharSetName
    :
    '_' (Letter | Digit | '_' | '-' | '.' | ':' )+
    ;

WS  :  (' '|'\r'|'\t'|'\n') {$channel=HIDDEN;}
    ;

COMMENT
  : '--' (~('\n'|'\r'))*
    { $channel=HIDDEN; }
  ;

statement


parser grammar HiveParser;

// starting rule
statement
    : explainStatement EOF
    | execStatement EOF
    ;

execStatement

execStatement
@init { pushMsg("statement", state); }
@after { popMsg(state); }
    : queryStatementExpression[true]
    | loadStatement
    | exportStatement
    | importStatement
    | ddlStatement
    | deleteStatement
    | updateStatement
    ;

以这个SQL为例:

select     
    dept.dept_no,     
    dept.dept_name,    
    salary_sum  
from (    
    select            
       dept_emp.dept_no,            
       sum(dept_emp.salary) salary_sum     
    from sql_parser.employee      
    join sql_parser.dept_emp      
    on employee.id = dept_emp.emp_no     
    group by dept_emp.dept_no 
 ) t 
 join department dept 
 on dept.dept_no = t.dept_no  
 order by salary_sum desc
 limits 10

这个SQL生成的AST树如下:

   TOK_QUERY
      TOK_FROM
         TOK_JOIN
            TOK_SUBQUERY
               TOK_QUERY
                  TOK_FROM
                     TOK_JOIN
                        TOK_TABREF
                           TOK_TABNAME
                              sql_parser
                              employee
                        TOK_TABREF
                           TOK_TABNAME
                              sql_parser
                              dept_emp
                        =
                           .
                              TOK_TABLE_OR_COL
                                 employee
                              id
                           .
                              TOK_TABLE_OR_COL
                                 dept_emp
                              emp_no
                  TOK_INSERT
                     TOK_DESTINATION
                        TOK_DIR
                           TOK_TMP_FILE
                     TOK_SELECT
                        TOK_SELEXPR
                           .
                              TOK_TABLE_OR_COL
                                 dept_emp
                              dept_no
                        TOK_SELEXPR
                           TOK_FUNCTION
                              sum
                              .
                                 TOK_TABLE_OR_COL
                                    dept_emp
                                 salary
                           salary_sum
                     TOK_GROUPBY
                        .
                           TOK_TABLE_OR_COL
                              dept_emp
                           dept_no
               t
            TOK_TABREF
               TOK_TABNAME
                  department
               dept
            =
               .
                  TOK_TABLE_OR_COL
                     dept
                  dept_no
               .
                  TOK_TABLE_OR_COL
                     t
                  dept_no
      TOK_INSERT
         TOK_DESTINATION
            TOK_DIR
               TOK_TMP_FILE
         TOK_SELECT
            TOK_SELEXPR
               .
                  TOK_TABLE_OR_COL
                     dept
                  dept_no
            TOK_SELEXPR
               .
                  TOK_TABLE_OR_COL
                     dept
                  dept_name
            TOK_SELEXPR
               TOK_TABLE_OR_COL
                  salary_sum
         TOK_ORDERBY
            TOK_TABSORTCOLNAMEDESC
               TOK_TABLE_OR_COL
                  salary_sum

AST 结点的结构如下:
每个结点都有一个名称,如“TOK_QUERY”。每个结点都可能有子节点。如“TOK_QUERY”的子节点是“TOK_FROM”和“TOK_INSERT”。

第二部分是逻辑分析

逻辑分析根据AST生成QueryBlock对象。
QueryBlock的内容如下:
Map

(    
 select            
dept_emp.dept_no,            
sum(employee.salary) salary_sum     
from sql_parser.employee      
join sql_parser.dept_emp      
on employee.id = dept_emp.emp_no     
group by dept_emp.dept_no ) t 

nameToDest的生成规则:anmeToDest是SQL语句中检索的内容。把整个TOK_SELECT 放到放到关键字为随机的nameToDest映射中。
如select dept.dept_no, dept.dept_name, salary_sum

TOK_SELECT
            TOK_SELEXPR
               .
                  TOK_TABLE_OR_COL
                     dept
                  dept_no
            TOK_SELEXPR
               .
                  TOK_TABLE_OR_COL
                     dept
                  dept_name
             TOK_SELEXPR
               TOK_TABLE_OR_COL
                  salary_sum

第三部分 语义分析和判断

从第二部分的逻辑分析,我们知道这一层的查询内容,以及这些表和子查询。我们的最终的目的是找到查询用的所有字段,判断是否是隐私字段,如果是隐私字段,我们判断用的方法是否允许。但是我们从QueryBlock得到一个字段,如salary_sum, 我们无法判断它对应哪个表的哪个字段。因此我们要有语义分析。语义分析根据QueryBlock生成SelectExprDependencyRelation对象。
语义分析的定义如下:

class SelectExprDependencyRelation {
  List<SelectItem> itemList; // 查询的字段
  Map<String, ITable> tableMap; // 查询用的表映射
// 子查询对应的语义分析对象 
  Map<Strng, SelectExprDependencyRelation> subRelation; 
  }

SelectItem的定义如下:

  Class SelectItem {
     String dbName; // 查询内容对应的库名;
     String tableName; // 查询内容对应的表名
     String fieldname; // 查询内容对应的字段名
     Set<String> funcSet; // 作用在字段上的函数名的集合
     String alias; // 查询别名,如果不配置,则查询别名为字段名fieldname。
}

itemList得到真正的字段列表。

语义分析过程如下:
1. 生成tableMap。每个QueryBlock的aliasToTabs中的一个映射对象生成tableMap的一个映射,key值相同。从aliasToTabs的对应的值找到数据库名和表名,根据数据库名和表名从元数据管理系统中得到ITable对象,放到tableMap中。
数据库名和表名的规则如下:
如果ASTNode有两个子结点,则第一个子节点是数据库名,第二个子节点是表名。如果ASTNode有一个子结点,则该子结点是表名,数据库名则是当前数据库名。
2. 生成subRelation。由于每个子查询又是一个QueryBlock,每个都生成对应的SelectExprDependencyRelation。这是一个递归过程,因为子查询可能还有子查询。每个子查询生成后放到subRelation中。
3. 生成itemList。由于这是最后一步,由于递归,所以最先生成最内部子查询的itemList,再生成上级查询的itemList。由于最内部的子查询是没有子查询的,可以找到查询内容对应的表的字段,把字段,表名,函数以及查询内容的别名组成一个个SelectItem,放到子查询对应的itemList中。解析上一层查询内容是,其对应的字段要么在当前层对应的表中,要么在当前层对应的子查询中。生成对应的SelectItem,放在本层的itemList,一直到最上层,从itemList就知道本查询的所有内容是从哪些表的哪些字段查询出来的。
由于到某一层时,所有的表和子查询对应的字段都已经清楚了,我们开发了两个个工具方法,名称为getSelectItemList。其中一个有一个参数为字段别名,返回从表或子查询的所有用到的字段信息列表。另外一个有两个参数,第一个参数为表别名或子查询别名,第二个参数为字段别名。用于从对应的表和子查询中找到对应的字段别名对应的字段列表。为什么上层的一个查询内容对应是一个列表呢?因为上层的查询内容,可能在某级子查询中,对应的是某级子查询的一个函数的输出,而这个函数用到了多个列,则这个查询内容对应的所有列的信息都应该放到本机查询中。
详细过程如下:
从QueryBlock的nameToDest对象,遍历所有子结点,子结点命名为selectNode.
如果selectNode有两个子结点,则第二个子结点为内容别名。
第一个子节点为查询内容,依次执行以下判断。
1. 是否为“TOK_ALLCOLREF”, 对应“select ”中的’’。再判断是否有限定,对应于“select t.*”。如果没有限定,则代表从所有表中,和子查询的所有查询内容都放到本层查询的查询结果中。如果有限定,则代表,则先根据限定名从tableMap找到对应的表。如果能找到,则检索出所有字段,每个字段组成一个SelectItem对象,放到itemList中。如果找到对应的表,则根据限定名从子查询中找到所有的itemList,全部加到当前层查询的itemList中。
2. 是否是“TOK_TABLE_OR_COL”,代表查询是字段。“TOK_TABLE_OR_COL”代表查询字段,用getSelectItemList方法找到该字段对应的所有SelectItem。每个SelectItem对象做拷贝,然后把函数集加上当前的函数,并且别名改为当前别名。
3. 是否是“.”,代表查询有限定。如“select dept.dept_no”, 限定了dept_no从表别名dept或者子查询dept中。用getSelectItemList方法找到该字段对应的所有SelectItem。每个SelectItem对象做拷贝,然后把函数集加上当前的函数,并且别名改为当前别名。
4. 其他情况,用于表达式,或函数,如“select func1(func2(column1), column2) ”, 由于一个函数的参数可能是函数或者是检索的字段,所以函数是一个嵌套的结构。如示例所述column1对应的函数为func1和func2,而column2对应的函数只有func1。如果参数为字段,则用以上的方法得到。而从该检索内容找到所有的字段和函数的方法getFieldsFromTree是一个递归式,这个递归式有四个参数,第一个参数为ASTNode node, 第二个参数为返回结果集List itemList,第三个参数为函数集Set functionSet。
从检索内容找到所有的字段和函数的方法getFieldsFromTree的执行步骤:
1. 生成新的函数集newFunctionSet;
2. 把所有的functionSet的内容添加到newFunctionSet。不能直接往functionSet中添加函数,因为递归式中里面的层级添加的函数上级应该看不到。
3. 判断node的内容:
3.1 如果是“字段”,则用getSelectItemList取出对应的字段,把当前然newFunctionSet的内容添加到字段的functionSet中,然后把字段添加到itemList中。然后返回。
3.2 如果是函数,则第一个子结点存储的是函数名,把函数名放到newFunctionSet中。从第二个子结点到最后一个子结点,依次调用getFieldsFromTree。
3.3 其他情况,则从第一个子结点到最后一个子结点,依次调用getFieldsFromTree。

我们以“select func1(func2(column1), column2) ”为例,结果为
column1的functionset 为func1,func2。
column2的functionset 为func1。

验证

从SelectExprDependencyRelation 对象取出itemList,从数据字典中找到表和字段,判断是否是隐私字段,如果是,从字段的functionSet取出函数,依次判断函数是否允许。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值