精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

探究Presto SQL引擎(1)-巧用Antlr

大數據
本文基于四則運算器和使用SQL查詢csv數據兩個案例闡述了ANTLR4在項目開發中的應用思路和過程,相關的代碼可以在github上看到。

一、背景

自2014年大數據首次寫入政府工作報告,大數據已經發展7年。大數據的類型也從交易數據延伸到交互數據與傳感數據。數據規模也到達了PB級別。

大數據的規模大到對數據的獲取、存儲、管理、分析超出了傳統數據庫軟件工具能力范圍。在這個背景下,各種大數據相關工具相繼出現,用于應對各種業務場景需求。從Hadoop生態的Hive, Spark, Presto, Kylin, Druid到非Hadoop生態的ClickHouse, Elasticsearch,不一而足...

這些大數據處理工具特性不同,應用場景不同,但是對外提供的接口或者說操作語言都是相似的,即各個組件都是支持SQL語言。只是基于不同的應用場景和特性,實現了各自的SQL方言。這就要求相關開源項目自行實現SQL解析。在這個背景下,誕生于1989年的語法解析器生成器ANTLR迎來了黃金時代。

二、簡介

ANTLR是開源的語法解析器生成器,距今已有30多年的歷史。是一個經歷了時間考驗的開源項目。一個程序從源代碼到機器可執行,基本需要3個階段:編寫、編譯、執行。

在編譯階段,需要進行詞法和語法的分析。ANTLR聚焦的問題就是把源碼進行詞法和句法分析,產生一個樹狀的分析器。ANTLR幾乎支持對所有主流編程語言的解析。從antlr/grammars-v4可以看到,ANTLR支持Java,C, Python, SQL等數十種編程語言。通常我們沒有擴展編程語言的需求,所以大部分情況下這些語言編譯支持更多是供學習研究使用,或者用在各種開發工具(NetBeans、Intellij)中用于校驗語法正確性、和格式化代碼。

對于SQL語言,ANTLR的應用廣度和深度會更大,這是由于Hive, Presto, SparkSQL等由于需要對SQL的執行進行定制化開發,比如實現分布式查詢引擎、實現各種大數據場景下獨有的特性等。

三、基于ANTLR4實現四則運算

當前我們主要使用的是ANTLR4。在《The Definitive ANTLR4 Reference》一書中,介紹了基于ANTLR4的各種有趣的應用場景。比如:實現一個支持四則運算的計算器;實現JSON等格式化文本的解析和提?。?

將JSON轉換成XML;從Java源碼中提取接口等。本節以實現四則運算計算器為例,介紹Antlr4的簡單應用,為后面實現基于ANTLR4解析SQL鋪平道路。實際上,支持數字運算也是各個編程語言必須具備的基本能力。

3.1 自行編碼實現

在沒有ANTLR4時,我們想實現四則運算該怎么處理呢?有一種思路是基于棧實現。例如,在不考慮異常處理的情況下,自行實現簡單的四則運算代碼如下:

package org.example.calc;
import java.util.*;
public class CalcByHand {
// 定義操作符并區分優先級,*/ 優先級較高
public static Set<String> opSet1 = new HashSet<>();
public static Set<String> opSet2 = new HashSet<>();
static{
opSet1.add("+");
opSet1.add("-");
opSet2.add("*");
opSet2.add("/");
}
public static void main(String[] args) {
String exp="1+3*4";
//將表達式拆分成token
String[] tokens = exp.split("((?<=[\\+|\\-|\\*|\\/])|(?=[\\+|\\-|\\*|\\/]))");
Stack<String> opStack = new Stack<>();
Stack<String> numStack = new Stack<>();
int proi=1;
// 基于類型放到不同的棧中
for(String token: tokens){
token = token.trim();
if(opSet1.contains(token)){
opStack.push(token);
proi=1;
}else if(opSet2.contains(token)){
proi=2;
opStack.push(token);
}else{
numStack.push(token);
// 如果操作數前面的運算符是高優先級運算符,計算后結果入棧
if(proi==2){
calcExp(opStack,numStack);
}
}
}
while (!opStack.isEmpty()){
calcExp(opStack,numStack);
}
String finalVal = numStack.pop();
System.out.println(finalVal);
}
private static void calcExp(Stack<String> opStack, Stack<String> numStack) {
double right=Double.valueOf(numStack.pop());
double left = Double.valueOf(numStack.pop());
String op = opStack.pop();
String val;
switch (op){
case "+":
val =String.valueOf(left+right);
break;
case "-":
val =String.valueOf(left-right);
break;
case "*":
val =String.valueOf(left*right);
break;
case "/":
val =String.valueOf(left/right);
break;
default:
throw new UnsupportedOperationException("unsupported");
}
numStack.push(val);
}
}

代碼量不大,用到了數據結構-棧的特性,需要自行控制運算符優先級,特性上沒有支持括號表達式,也沒有支持表達式賦值。接下來看看使用ANTLR4實現。

3.2 基于ANTLR4實現

使用ANTLR4編程的基本流程是固定的,通常分為如下三步:

  • 基于需求按照ANTLR4的規則編寫自定義語法的語義規則, 保存成以g4為后綴的文件。
  • 使用ANTLR4工具處理g4文件,生成詞法分析器、句法分析器代碼、詞典文件。
  • 編寫代碼繼承Visitor類或實現Listener接口,開發自己的業務邏輯代碼。

基于上面的流程,我們借助現有案例剖析一下細節。

第一步:基于ANTLR4的規則定義語法文件,文件名以g4為后綴。例如實現計算器的語法規則文件命名為LabeledExpr.g4。其內容如下:

grammar LabeledExpr; // rename to distinguish from Expr.g4
prog: stat+ ;
stat: expr NEWLINE # printExpr
| ID '=' expr NEWLINE # assign
| NEWLINE # blank
;
expr: expr op=('*'|'/') expr # MulDiv
| expr op=('+'|'-') expr # AddSub
| INT # int
| ID # id
| '(' expr ')' # parens
;
MUL : '*' ; // assigns token name to '*' used above in grammar
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;
ID : [a-zA-Z]+ ; // match identifiers
INT : [0-9]+ ; // match integers
NEWLINE:'\r'? '\n' ; // return newlines to parser (is end-statement signal)
WS : [ \t]+ -> skip ; // toss out whitespace

(注:此文件案例來源于《The Definitive ANTLR4 Reference》)

簡單解讀一下LabeledExpr.g4文件。ANTLR4規則是基于正則表達式定義定義。規則的理解是自頂向下的,每個分號結束的語句表示一個規則 。例如第一行:grammar LabeledExpr; 表示我們的語法名稱是LabeledExpr, 這個名字需要跟文件名需要保持一致。Java編碼也有相似的規則:類名跟類文件一致。

  • 規則prog 表示prog是一個或多個stat。
  • 規則stat 適配三種子規則:空行、表達式expr、賦值表達式 ID’=’expr。
  • 表達式expr適配五種子規則:乘除法、加減法、整型、ID、括號表達式。很顯然,這是一個遞歸的定義。

最后定義的是組成復合規則的基礎元素,比如:

  • 規則ID: [a-zA-Z]+表示ID限于大小寫英文字符串;
  • INT: [0-9]+; 表示INT這個規則是0-9之間的一個或多個數字,當然這個定義其實并不嚴格。再嚴格一點,應該限制其長度。

在理解正則表達式的基礎上,ANTLR4的g4語法規則還是比較好理解的。  

定義ANTLR4規則需要注意一種情況,即可能出現一個字符串同時支持多種規則,如以下的兩個規則:

  • ID: [a-zA-Z]+;
  • FROM: ‘from’;

很明顯,字符串” from”同時滿足上述兩個規則,ANTLR4處理的方式是按照定義的順序決定。這里ID定義在FROM前面,所以字符串from會優先匹配到ID這個規則上。

其實在定義好與法規中,編寫完成g4文件后,ANTLR4已經為我們完成了50%的工作:幫我們實現了整個架構及接口了,剩下的開發工作就是基于接口或抽象類進行具體的實現。實現上有兩種方式來處理生成的語法樹,其一Visitor模式,另一種方式是Listener(監聽器模式)。

3.2.1 使用Visitor模式

第二步:使用ANTLR4工具解析g4文件,生成代碼。即ANTLR工具解析g4文件,為我們自動生成基礎代碼。流程圖示如下:

命令行如下:

antlr4 -package org.example.calc -no-listener -visitor .\LabeledExpr.g4

命令執行完成后,生成的文件如下:

$ tree .
├── LabeledExpr.g4
├── LabeledExpr.tokens
├── LabeledExprBaseVisitor.java
├── LabeledExprLexer.java
├── LabeledExprLexer.tokens
├── LabeledExprParser.java
└── LabeledExprVisitor.java

首先開發入口類Calc.java。Calc類是整個程序的入口,調用ANTLR4的lexer和parser類核心代碼如下:

ANTLRInputStream input = new ANTLRInputStream(is);
LabeledExprLexer lexer = new LabeledExprLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
LabeledExprParser parser = new LabeledExprParser(tokens);
ParseTree tree = parser.prog(); // parse
EvalVisitor eval = new EvalVisitor();
eval.visit(tree);

接下來定義類繼承LabeledExprBaseVisitor類,覆寫的方法如下:

從圖中可以看出,生成的代碼和規則定義是對應起來的。例如visitAddSub對應AddSub規則,visitId對應id規則。以此類推…實現加減法的代碼如下:

/** expr op=('+'|'-') expr */
@Override
public Integer visitAddSub(LabeledExprParser.AddSubContext ctx) {
int left = visit(ctx.expr(0)); // get value of left subexpression
int right = visit(ctx.expr(1)); // get value of right subexpression
if ( ctx.op.getType() == LabeledExprParser.ADD ) return left + right;
return left - right; // must be SUB
}

相當直觀。代碼編寫完成后,就是運行Calc。運行Calc的main函數,在交互命令行輸入相應的運算表達式,換行Ctrl+D即可看到運算結果。例如1+3*4=13。

3.2.2 使用Listener模式

類似的,我們也可以使用Listener模式實現四則運算。命令行如下:

antlr4 -package org.example.calc -listener .\LabeledExpr.g4

該命令的執行同樣會為我們生產框架代碼。在框架代碼的基礎上,我們開發入口類和接口實現類即可。首先開發入口類Calc.java。Calc類是整個程序的入口,調用ANTLR4的lexer和parser類代碼如下:

ANTLRInputStream input = new ANTLRInputStream(is);
LabeledExprLexer lexer = new LabeledExprLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
LabeledExprParser parser = new LabeledExprParser(tokens);
ParseTree tree = parser.prog(); // parse
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(new EvalListener(), tree);

可以看出生成ParseTree的調用邏輯一模一樣。實現Listener的代碼略微復雜一些,也需要用到棧這種數據結構,但是只需要一個操作數棧就可以了,也無需自行控制優先級。以AddSub為例:

@Override
public void exitAddSub(LabeledExprParser.AddSubContext ctx) {
Double left = numStack.pop();
Double right= numStack.pop();
Double result;
if (ctx.op.getType() == LabeledExprParser.ADD) {
result = left + right;
} else {
result = left - right;
}
numStack.push(result);
}

直接從棧中取出操作數,進行運算即可。

3.2.3 小結

關于Listener模式和Visitor模式的區別,《The Definitive ANTLR 4 Reference》一書中有清晰的解釋:

Listener模式:

Visitor模式

  • Listener模式通過walker對象自行遍歷,不用考慮其語法樹上下級關系。Vistor需要自行控制訪問的子節點,如果遺漏了某個子節點,那么整個子節點都訪問不到了。
  • Listener模式的方法沒有返回值,Vistor模式可以設定任意返回值。
  • Listener模式的訪問棧清晰明確,Vistor模式是方法調用棧,如果實現出錯有可能導致StackOverFlow。

通過這個簡單的例子,我們驅動Antlr4實現了一個簡單的計算器。學習了ANTLR4的應用流程。了解了g4語法文件的定義方式、Visitor模式和Listener模式。通過ANTLR4,我們生成了ParseTree,并基于Visitor模式和Listener模式訪問了這個ParseTree,實現了四則運算。

綜合上述的例子可以發現,如果沒有ANTLR4,我們自行編寫算法也能實現同樣的功能。但是使用ANTLR不用關心表達式串的解析流程,只關注具體的業務實現即可,非常省心和省事。

更重要的是,ANTLR4相比自行實現提供了更具想象空間的抽象邏輯,上升到了方法論的高度,因為它已經不局限于解決某個問題,而是解決一類問題??梢哉fANTLR相比于自行硬編碼解決問題的思路有如數學領域普通的面積公式和微積分的差距。

四、參考Presto源碼開發SQL解析器

前面介紹了使用ANTLR4實現四則運算,其目的在于理解ANTLR4的應用方式。接下來圖窮匕首見,展示出我們的真正目的:研究ANTLR4在Presto中如何實現SQL語句的解析。

支持完整的SQL語法是一個龐大的工程。在presto中有完整的SqlBase.g4文件,定義了presto支持的所有SQL語法,涵蓋了DDL語法和DML語法。該文件體系較為龐大,并不適合學習探究某個具體的細節點。

為了探究SQL解析的過程,理解SQL執行背后的邏輯,在簡單地閱讀相關資料文檔的基礎上,我選擇自己動手編碼實驗。為此,定義一個小目標:實現一個SQL解析器。用該解析器實現select field from table語法,從本地的csv數據源中查詢指定的字段。

4.1 裁剪SelectBase.g4文件

基于同實現四則運算器同樣的流程,首先定義SelectBase.g4文件。由于有了Presto源碼作為參照系,我們的SelectBase.g4并不需要自己開發,只需要基于Presto的g4文件裁剪即可。裁剪后的內容如下:

grammar SqlBase;
tokens {
DELIMITER
}
singleStatement
: statement EOF
;
statement
: query #statementDefault
;
query
: queryNoWith
;
queryNoWith:
queryTerm
;
queryTerm
: queryPrimary #queryTermDefault
;
queryPrimary
: querySpecification #queryPrimaryDefault
;
querySpecification
: SELECT selectItem (',' selectItem)*
(FROM relation (',' relation)*)?
;
selectItem
: expression #selectSingle
;
relation
: sampledRelation #relationDefault
;
expression
: booleanExpression
;
booleanExpression
: valueExpression #predicated
;
valueExpression
: primaryExpression #valueExpressionDefault
;
primaryExpression
: identifier #columnReference
;
sampledRelation
: aliasedRelation
;
aliasedRelation
: relationPrimary
;
relationPrimary
: qualifiedName #tableName
;
qualifiedName
: identifier ('.' identifier)*
;
identifier
: IDENTIFIER #unquotedIdentifier
;
SELECT: 'SELECT';
FROM: 'FROM';
fragment DIGIT
: [0-9]
;
fragment LETTER
: [A-Z]
;
IDENTIFIER
: (LETTER | '_') (LETTER | DIGIT | '_' | '@' | ':')*
;
WS
: [ \r\n\t]+ -> channel(HIDDEN)
;
// Catch-all for anything we can't recognize.
// We use this to be able to ignore and recover all the text
// when splitting statements with DelimiterLexer
UNRECOGNIZED
: .
;

相比presto源碼中700多行的規則,我們裁剪到了其1/10的大小。該文件的核心規則為: SELECT  selectItem (',' selectItem)*  (FROM relation (',' relation)*)?

通過理解g4文件,也可以更清楚地理解我們查詢語句的構成。例如通常我們最常見的查詢數據源是數據表。但是在SQL語法中,我們查詢數據表被抽象成了relation。

這個relation有可能來自于具體的數據表,或者是子查詢,或者是JOIN,或者是數據的抽樣,或者是表達式的unnest。在大數據領域,這樣的擴展會極大方便數據的處理。

例如,使用unnest語法解析復雜類型的數據,SQL如下:

盡管SQL較為復雜,但是通過理解g4文件,也能清晰理解其結構劃分?;氐絊electBase.g4文件,同樣我們使用Antlr4命令處理g4文件,生成代碼:

antlr4 -package org.example.antlr -no-listener -visitor .\SqlBase.g4

這樣就生成了基礎的框架代碼。接下來就是自行處理業務邏輯的工作了。

4.2 遍歷語法樹封裝SQL結構信息

接下來基于SQL語法定義語法樹的節點類型,如下圖所示。

通過這個類圖,可以清晰明了看清楚SQL語法中的各個基本元素。

然后基于visitor模式實現自己的解析類AstBuilder (這里為了簡化問題,依然從presto源碼中進行裁剪)。以處理querySpecification規則代碼為例:

@Override
public Node visitQuerySpecification(SqlBaseParser.QuerySpecificationContext context)
{
Optional<Relation> from = Optional.empty();
List<SelectItem> selectItems = visit(context.selectItem(), SelectItem.class);
List<Relation> relations = visit(context.relation(), Relation.class);
if (!relations.isEmpty()) {
// synthesize implicit join nodes
Iterator<Relation> iterator = relations.iterator();
Relation relation = iterator.next();
from = Optional.of(relation);
}
return new QuerySpecification(
getLocation(context),
new Select(getLocation(context.SELECT()), false, selectItems),
from);
}

通過代碼,我們已經解析出了查詢的數據源和具體的字段,封裝到了QuerySpecification對象中。

4.3 應用Statement對象實現數據查詢

通過前面實現四則運算器的例子,我們知道ANTLR把用戶輸入的語句解析成ParseTree。業務開發人員自行實現相關接口解析ParseTree。Presto通過對輸入sql語句的解析,生成ParseTree, 對ParseTree進行遍歷,最終生成了Statement對象。核心代碼如下:

SqlParser sqlParser = new SqlParser();
Statement statement = sqlParser.createStatement(sql);

有了Statement對象我們如何使用呢?結合前面的類圖,我們可以發現:

  •  Query類型的Statement有QueryBody屬性。
  • QuerySpecification類型的QueryBody有select屬性和from屬性。

通過這個結構,我們可以清晰地獲取到實現select查詢的必備元素:

  • 從from屬性中獲取待查詢的目標表Table。這里約定表名和csv文件名一致。
  • 從select屬性中獲取待查詢的目標字段SelectItem。這里約定csv首行為title行。

整個業務流程就清晰了,在解析sql語句生成statement對象后,按如下的步驟:

s1: 獲取查詢的數據表以及字段。

s2: 通過數據表名稱定為到數據文件,并讀取數據文件數據。

s3: 格式化輸出字段名稱到命令行。

s4: 格式化輸出字段內容到命令行。

為了簡化邏輯,代碼只處理主線,不做異常處理。

/**
* 獲取待查詢的表名和字段名稱
*/
QuerySpecification specification = (QuerySpecification) query.getQueryBody();
Table table= (Table) specification.getFrom().get();
List<SelectItem> selectItems = specification.getSelect().getSelectItems();
List<String> fieldNames = Lists.newArrayList();
for(SelectItem item:selectItems){
SingleColumn column = (SingleColumn) item;
fieldNames.add(((Identifier)column.getExpression()).getValue());
}
/**
* 基于表名確定查詢的數據源文件
*/
String fileLoc = String.format("./data/%s.csv",table.getName());
/**
* 從csv文件中讀取指定的字段
*/
Reader in = new FileReader(fileLoc);
Iterable<CSVRecord> records = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in);
List<Row> rowList = Lists.newArrayList();
for(CSVRecord record:records){
Row row = new Row();
for(String field:fieldNames){
row.addColumn(record.get(field));
}
rowList.add(row);
}
/**
* 格式化輸出到控制臺
*/
int width=30;
String format = fieldNames.stream().map(s-> "%-"+width+"s").collect(Collectors.joining("|"));
System.out.println( "|"+String.format(format, fieldNames.toArray())+"|");
int flagCnt = width*fieldNames.size()+fieldNames.size();
String rowDelimiter = String.join("", Collections.nCopies(flagCnt, "-"));
System.out.println(rowDelimiter);
for(Row row:rowList){
System.out.println( "|"+String.format(format, row.getColumnList().toArray())+"|");
}

代碼僅供演示功能,暫不考慮異常邏輯,比如查詢字段不存在、csv文件定義字段名稱不符合要求等問題。

4.4 實現效果展示

在我們項目data目錄,存儲如下的csv文件:

cities.csv文件樣例數據如下:

"LatD","LatM","LatS","NS","LonD","LonM","LonS","EW","City","State"
41, 5, 59, "N", 80, 39, 0, "W", "Youngstown", OH
42, 52, 48, "N", 97, 23, 23, "W", "Yankton", SD
46, 35, 59, "N", 120, 30, 36, "W", "Yakima", WA
42, 16, 12, "N", 71, 48, 0, "W", "Worcester", MA

運行代碼查詢數據。使用SQL語句指定字段從csv文件中查詢。最終實現類似SQL查詢的效果如下:

SQL樣例1:select City, City from cities

SQL樣例2:select name, age from employee

本節講述了如何基于Presto源碼,裁剪g4規則文件,然后基于Antlr4實現用sql語句從csv文件查詢數據。依托于對Presto源碼的裁剪進行編碼實驗,對于研究SQL引擎實現,理解Presto源碼能起到一定的作用。

五、總結

本文基于四則運算器和使用SQL查詢csv數據兩個案例闡述了ANTLR4在項目開發中的應用思路和過程,相關的代碼可以在github上看到。理解ANTLR4的用法能夠幫助理解SQL的定義規則及執行過程,輔助業務開發中編寫出高效的SQL語句。同時對于理解編譯原理,定義自己的DSL,抽象業務邏輯也大有裨益。紙上得來終覺淺,絕知此事要躬行。通過本文描述的方式研究源碼實現,也不失為一種樂趣。

責任編輯:龐桂玉 來源: vivo互聯網技術
相關推薦

2022-10-27 11:31:10

Presto SQL統計計數大數據

2022-10-27 10:32:09

Presto SQLJoin大數據

2022-10-27 11:07:40

2025-03-06 04:00:00

2010-09-26 10:35:47

sql替換語句

2010-09-16 17:56:31

SQL server臨

2011-03-09 14:18:37

SQL數據累加

2025-08-07 01:00:00

2018-12-25 14:40:04

SQL ServerSQL語句數據庫

2010-09-25 16:17:25

SQL語句

2010-09-09 11:17:44

SQL函數公歷

2022-09-27 21:22:02

SQL Server數據庫

2011-03-07 13:27:13

SQLCase

2022-09-29 19:37:09

SQL Server數據庫

2017-01-19 19:46:21

Opera Prest代碼瀏覽器

2011-08-02 15:39:30

SQL Server iSql

2010-09-07 14:45:34

sql語句

2010-09-14 13:30:28

sql server備

2018-12-26 09:25:30

SQL ServerSQL語句數據庫

2022-09-05 17:09:55

SQL Server數據庫
點贊
收藏

51CTO技術棧公眾號

欧美激情精品久久久久久小说| 欧美一级xxx| 亚洲人精品午夜| 91久久久在线| 日韩网红少妇无码视频香港| 激情综合网站| 欧美xxxx老人做受| 亚洲视频在线观看一区二区三区| av在线免费观看网址| 91在线观看污| 亚洲一区二区三区久久| 国内自拍视频在线播放| 在线一区免费| 国产亚洲综合久久| 97精品人人妻人人| 日本一区二区中文字幕| 欧美日韩亚洲精品一区二区三区| 一区精品在线| 免费一级在线观看播放网址| 精品在线一区二区三区| 欧美一性一乱一交一视频| 精品无码一区二区三区蜜臀| 理论片一区二区在线| 欧美日韩二区三区| 丰满人妻中伦妇伦精品app| 午夜在线激情影院| 国产精品入口麻豆原神| 久久亚洲国产精品日日av夜夜| 国产男男gay网站| 天堂资源在线中文精品| 久久人人爽人人| 欧美大片xxxx| 亚洲无人区码一码二码三码的含义| 台湾av在线二三区观看| 国产suv精品一区二区883| 国产精品福利网站| www毛片com| 国产欧美一区二区三区国产幕精品| 久久综合免费视频| 国产黄色录像片| 视频一区在线观看| 日韩精品中文字| 国产chinese中国hdxxxx| 精品视频一区二区三区在线观看| 欧美性一级生活| 国产成人黄色片| 蜜桃视频在线观看播放| 亚洲香肠在线观看| 国产制服91一区二区三区制服| 日本最新在线视频| 国产精品无人区| 日韩精品极品视频在线观看免费| 青青免费在线视频| 久久久久9999亚洲精品| 久久亚洲一区二区| 免费一级在线观看播放网址| 久久久另类综合| 茄子视频成人在线观看| 黄色免费在线播放| 国产人成一区二区三区影院| 日韩欧美电影一区二区| wwwww在线观看免费视频| 欧美激情一二三区| 一区二区三区四区| caoporn免费在线视频| 一区二区三区免费在线观看| 2022中文字幕| 白浆视频在线观看| 欧美日韩一区二区免费视频| 人妻有码中文字幕| 成人福利片在线| 欧美一区二区免费视频| 日本一区二区免费视频| 亚洲精品进入| 伊人久久综合97精品| 国产在线免费看| 欧美三区美女| 欧美中文在线字幕| 亚洲熟妇无码久久精品| 国产精品99久久久久久似苏梦涵| 国产成人女人毛片视频在线| 青青草视频在线免费观看| 欧美国产一区二区在线观看| 女女同性女同一区二区三区按摩| 黑人精品视频| 欧美在线观看禁18| 五月六月丁香婷婷| 亚洲女娇小黑人粗硬| 中文字幕一区电影| 国产一级做a爱免费视频| 噜噜噜躁狠狠躁狠狠精品视频 | 三级在线观看免费大全| 亚洲天堂偷拍| 国产精品视频一区二区三区四| 国产成a人亚洲精v品无码| 99国内精品久久| 一区二区精品国产| 精品捆绑调教一区二区三区| 欧美日韩激情在线| 欧美xxxxx少妇| 精品99久久| 欧美激情中文字幕乱码免费| 久久久久久久久久成人| 国产黑丝在线一区二区三区| 日本福利一区二区三区| 日本三级韩国三级欧美三级| 色国产精品一区在线观看| 欧美人与性动交α欧美精品| 色综合www| 久久久国产一区二区三区| 国偷自拍第113页| 国产在线观看免费一区| 欧美另类网站| 91豆花视频在线播放| 欧美日韩在线播放一区| 人妻丰满熟妇aⅴ无码| 91精品秘密在线观看| 国产91色在线|| 天天操天天干天天爽| 亚洲另类在线视频| 日本人69视频| 国产一区99| 国产麻豆精品在线| 欧美一性一乱一交一视频| av片免费播放| 国产精品国产馆在线真实露脸| 日韩免费一级视频| 都市激情亚洲| 色综合视频一区中文字幕| 在线免费观看日韩视频| 国产视频一区二区在线| 激情五月宗合网| 成人激情自拍| 欧美精品手机在线| 国产偷人妻精品一区二区在线| 亚洲国产精品黑人久久久 | 日韩精品免费专区| 欧美日韩一区二区三区免费| 97超碰在线免费| 亚洲福利在线播放| 日本三级黄色大片| 成人午夜在线免费| 黄色激情在线视频| 亚洲一区二区三区免费| 久久躁狠狠躁夜夜爽| 97免费观看视频| 亚洲欧美在线另类| 手机在线国产视频| 亚洲欧美综合久久久| 成人妇女淫片aaaa视频| 国产在线观看a| 91精品国产综合久久精品| 三上悠亚在线观看视频| 国内久久婷婷综合| 成人在线免费高清视频| 日韩高清一区| 久久久在线视频| 日本a一级在线免费播放| 色婷婷综合视频在线观看| www.中文字幕av| 青青草97国产精品免费观看 | 一区二区三区福利| 久久av一区二区三区漫画| 在线日韩影院| 伊人伊成久久人综合网小说 | 亚洲国产成人精品久久| 国产福利拍拍拍| wwww国产精品欧美| 亚欧在线免费观看| 久久精品欧美一区| 国产精品我不卡| 忘忧草在线影院两性视频| 亚洲欧美变态国产另类| 亚洲无码精品国产| 一二三四社区欧美黄| 天天插天天射天天干| 日韩中文字幕av电影| 国产高清精品软男同| 亚洲开心激情| 青青久久av北条麻妃海外网| 最新电影电视剧在线观看免费观看| 制服丝袜中文字幕亚洲| 日本一区二区不卡在线| 国产欧美日本一区视频| 伊人影院在线观看视频| 久久精品1区| 日本女人高潮视频| 亚洲va久久| 91亚洲精品一区| 小视频免费在线观看| 精品国产一区二区在线| 少妇人妻一区二区| 欧美视频一区二区| 国产奶水涨喷在线播放| 中文字幕乱码亚洲精品一区 | 福利片在线一区二区| 国产极品精品在线观看| 成人看av片| 亚洲视频欧美视频| 亚洲精品国产suv一区| 91久久线看在观草草青青| 激情五月婷婷在线| 欧美国产激情二区三区| 亚洲自拍偷拍精品| 激情欧美日韩一区二区| 六月丁香婷婷在线| 欧美午夜不卡| 一区在线电影| 奇米狠狠一区二区三区| 51国偷自产一区二区三区 | 欧美视频精品在线观看| 国产网友自拍视频| 亚洲视频免费看| av女人的天堂| 99精品欧美一区二区三区小说 | 国产精品理论片在线观看| 水蜜桃av无码| 日本在线播放视频| 99在线视频精品| ass极品水嫩小美女ass| 日韩精品每日更新| 日日橹狠狠爱欧美超碰| 欧美日韩理论| 性欧美18一19内谢| 欧美综合另类| 欧美一区2区三区4区公司二百| av日韩在线播放| 91在线免费看网站| 国产精品黄色片| 国产精品18久久久久久首页狼| 爱看av在线入口| 操人视频在线观看欧美| 快射视频在线观看| 久久精品中文字幕免费mv| freemovies性欧美| 亚洲天堂男人的天堂| 深夜视频在线免费| 国产午夜精品麻豆| 天天综合天天色| 亚洲成人激情图| 黄片毛片在线看| 精品噜噜噜噜久久久久久久久试看 | 日本黄视频在线观看| 欧美成人a∨高清免费观看| av男人天堂av| 日韩精品影音先锋| 精品国产黄色片| 日韩亚洲国产中文字幕欧美| 精品女同一区二区三区| 欧美一级久久久久久久大片| av中文字幕免费在线观看| 日韩精品一区二区三区在线观看| 国产成人精品免费看视频| 欧美r级电影在线观看| 亚洲精品一区二区三区区别| 精品国产人成亚洲区| 成人毛片视频免费看| 日韩电视剧免费观看网站| 免费资源在线观看| 永久免费毛片在线播放不卡| 18视频免费网址在线观看| 日韩在线观看你懂的| 欧美人xxx| 欧美精品免费播放| 久久青草伊人| 国产成人精品a视频一区www| 国产成人精选| www久久99| 日韩欧美黄色| 日韩精品伦理第一区| 外国成人激情视频| 国产中文字幕乱人伦在线观看| 99精品国产99久久久久久福利| 日本一本二本在线观看| 麻豆精品在线看| 色诱av手机版| 久久久久久综合| 亚洲天堂一级片| 天天av天天翘天天综合网色鬼国产| 亚洲av中文无码乱人伦在线视色| 欧美日韩一二三区| 亚洲精品一区二区三区蜜桃 | 国产在线观看av| 668精品在线视频| 青草综合视频| 国产一区二区在线网站| 国产日韩欧美一区二区三区| 椎名由奈jux491在线播放 | 91精品国产91| 国产成人亚洲一区二区三区| 国产精品久久7| 精品视频免费| av免费看网址| 老司机午夜精品99久久| www国产视频| 中文字幕日韩欧美一区二区三区| 69精品久久久| 欧美人伦禁忌dvd放荡欲情| 日韩一级片免费观看| 国产亚洲激情视频在线| 51漫画成人app入口| 国产精品偷伦视频免费观看国产| www国产精品| 欧美日韩一级在线| 久久综合中文| 永久看看免费大片| 欧美高清在线精品一区| 日韩精品一卡二卡| 777色狠狠一区二区三区| 欧美成熟毛茸茸| 久久久久免费视频| 欧洲亚洲精品久久久久| 欧美一卡2卡3卡4卡无卡免费观看水多多 | 欧美一二三区视频| 欧美高清视频在线高清观看mv色露露十八 | 日韩国产精品一区| 羞羞污视频在线观看| 国产欧美日韩91| 精品国产一区二区三区久久久樱花 | 亚洲影视综合| 岛国av免费观看| 亚洲人成电影网站色mp4| 中文字幕永久免费视频| 亚洲精品久久视频| 暖暖在线中文免费日本| 成人在线播放av| 欧美韩日一区| 黄色aaa级片| 福利精品在线| 亚洲偷熟乱区亚洲香蕉av| 国产蜜臀一区二区打屁股调教| 国产欧美最新羞羞视频在线观看| 国产精品美女久久久久久不卡| 欧美亚洲日本一区二区三区| 床上的激情91.| 青娱乐国产精品| 欧美一级欧美三级| gogo在线观看| 91中文字幕在线观看| 亚洲精品888| 爽爽爽在线观看| 亚洲人成7777| 性少妇videosexfreexxx片| 欧美成年人视频网站欧美| 秋霞一区二区| 国产成人生活片| 国产成人精品亚洲777人妖| 欧美日韩精品在线观看视频| 日韩免费性生活视频播放| 尤物视频在线看| 国产精品国产精品| 国产精品久久777777毛茸茸 | 九九在线高清精品视频| 久久久久久久久久久久久国产精品| 91网址在线看| 欧美性猛交bbbbb精品| 亚洲系列中文字幕| 欧美成人免费全部网站| 宅男在线精品国产免费观看| 韩国午夜理伦三级不卡影院| 久久久久久久久久网站| 精品久久五月天| rebdb初裸写真在线观看| 女女同性女同一区二区三区91| 日韩经典一区二区| 亚洲波多野结衣| 精品国产精品一区二区夜夜嗨| 九色porny丨国产首页在线| 欧美中日韩免费视频| 久久99精品网久久| 久久精品www| 亚洲人成电影网站| 日韩欧乱色一区二区三区在线| 国产精品88久久久久久妇女 | 欧美一区二区女人| 波多野结衣精品| 青青影院一区二区三区四区| 老司机午夜精品99久久| www.99re7.com| 一区二区在线视频| 欧美专区一区| av网站在线观看不卡| 国产精品电影院| 日本xxxx人| 国产日本欧美在线观看| 极品中文字幕一区| 日韩精品电影一区二区三区| 日韩丝袜情趣美女图片| 成人爽a毛片免费啪啪| 综合视频在线观看| 91丨九色丨尤物| 国产又粗又猛又爽又黄视频 | youjizz在线视频| 久久精品视频导航| 亚洲+变态+欧美+另类+精品| 久久久久久久久久一区二区| 精品高清美女精品国产区| 视频三区在线| 蜜桃麻豆91| 国产69精品久久99不卡|