* Version : 0.12 * Date : 7 Jan, 2009 * Purpose : Convert SQL Query (only DQL(SELECT) and DML(INSERT, UPDATE, DELETE)) to TREE */ class dqml2tree { var $sql = ''; var $_sql = ''; var $_as = Array(); var $_base_rules = Array(); var $_inter_rules = Array(); var $_noval_rules = Array(); var $_sheet_rules = Array(); var $_table_rules = Array(); var $_list_base_rules = Array(); var $_zonesQ = Array(); var $_zonesP = Array(); var $_tree = Array(); var $_base_tree = Array(); var $_inter_tree = Array(); var $_zonesP_tree = Array(); var $_lexs = Array(); function __construct( $query ) { $this->_base_rules['SQL'] = Array( 'SELECT', 'INSERT', 'UPDATE', 'DELETE' ); $this->_base_rules['SELECT'] = Array( 'FROM', 'WHERE', 'GROUP', 'HAVING', 'UNION', 'INTERSECT', 'MINUS', 'EXCEPT', 'ORDER', 'LIMIT', 'OFFSET' ); $this->_base_rules['UPDATE'] = Array( 'SET', 'FROM', 'WHERE' ); $this->_base_rules['INSERT'] = Array( 'INTO', 'VALUES' ); $this->_base_rules['DELETE'] = Array( 'FROM', 'WHERE' ); $this->_base_rules['UNION'] = Array( 'SELECT' ); $this->_base_rules['INTERSECT'] = Array( 'SELECT' ); $this->_base_rules['MINUS'] = Array( 'SELECT' ); $this->_base_rules['EXCEPT'] = Array( 'SELECT' ); foreach ( $this->_base_rules as $key => $vals ) { $this->_list_base_rules[ $key ] = $key; foreach ( $vals as $val ) { $this->_list_base_rules[ $val ] = $val; } } $this->_inter_rules['SELECT'] = Array( '*SELECT' => ',' ); $this->_inter_rules['UPDATE'] = Array( '*UPDATE' => ',' ); $this->_inter_rules['INTO'] = Array( '*INSERT' => ' (' ); $this->_inter_rules['*INSERT'] = Array( '*INTO' => ',' ); $this->_inter_rules['FROM'] = Array( '*FROM' => ',' ); $this->_inter_rules['WHERE'] = Array( '*OR' => ' OR ' ); $this->_inter_rules['HAVING'] = Array( '*OR' => ' OR ' ); $this->_inter_rules['GROUP'] = Array( '*GROUP' => ',' ); $this->_inter_rules['ORDER'] = Array( '*ORDER' => ',' ); $this->_inter_rules['LIMIT'] = Array( '*LIMIT' => ',' ); $this->_inter_rules['VALUES'] = Array( '*VALUES' => ',' ); $this->_inter_rules['SET'] = Array( '*SET' => ',' ); $this->_inter_rules['*FROM'] = Array( '*JOIN' => ' JOIN ' ); $this->_inter_rules['*JOIN'] = Array( '*ON' => ' ON ' ); $this->_inter_rules['*ON'] = Array( '*OR' => ' OR ' ); $this->_inter_rules['*OR'] = Array( '*AND' => ' AND ' ); $this->_inter_rules['*SET'] = Array( '#SET' => '=' ); $this->_inter_rules['*AND'] = Array( '!IN' => ' IN ', '!IS' => ' IS ', '!DIFF' => '<>', '!EQLESS' => '<=', '!EQPLUS' => '>=', '!EQ' => '=', '!LESS' => '<', '!PLUS' => '>', '!LIKE' => ' LIKE ', '!ILIKE' => ' ILIKE ' ); $this->_as = Array( '*AS' => ' AS ', '*AS' => ' ' ); $this->_inter_rules['*SELECT'] = $this->_as; $this->_inter_rules['0|*JOIN'] = $this->_as; $this->_inter_rules['0|*ON'] = $this->_as; $this->_noval_rules = Array( '', ',', 'AS', 'BY', 'OR', 'AND', 'ON', 'JOIN' ); $this->_sheet_rules = Array( 'ORDER' => Array( 'ASC', 'DESC' ), '*ORDER' => Array( 'ASC', 'DESC' ), '*JOIN' => Array( 'INNER', 'OUTER', 'LEFT', 'RIGHT' ), '*ON' => Array( 'INNER', 'OUTER', 'LEFT', 'RIGHT' ) ); foreach ( $this->_sheet_rules['*ON'] as $j ) { $this->_noval_rules[] = 'JOIN ' . $j; } $this->_table_rules = Array( 'FROM', 'JOIN', 'ON', 'INSERT', 'UPDATE', '*FROM', '*JOIN', '*ON', '*INSERT', '*UPDATE' ); $this->sql = $query; $this->_tree['SQL']['_a'] = 0; $this->_tree['SQL']['_z'] = strlen( $query ); } function _reformatQuery() { $_sql = $this->sql; $_sql = strtoupper( $_sql ); $_sql = preg_replace( '/;[\s\n]*$/', '', $_sql ); $_sql = str_replace( "\'", "''", $_sql ); $_sql = str_replace( "\r", ' ', $_sql ); $_sql = str_replace( "\n", ' ', $_sql ); $_sql = str_replace( ' INNER JOIN ', ' JOIN INNER ', $_sql ); $_sql = str_replace( ' OUTER JOIN ', ' JOIN OUTER ', $_sql ); $_sql = str_replace( ' LEFT JOIN ', ' JOIN LEFT ', $_sql ); $_sql = str_replace( ' RIGHT JOIN ', ' JOIN RIGHT ', $_sql ); $this->_sql = $_sql; } function _zonesQuote() { $char = "'"; $sub_sql = $this->_sql; $pos = strpos( $sub_sql, $char ); $index = 0; $etat = false; while ( $pos !== false ) { $index += $pos; $sub_sql = substr( $sub_sql, $pos + 1 ); $sub_sql = ' ' . $sub_sql; $etat = ( $etat == false ? true : false ); $this->_zonesQ[ $index ] = $etat; $pos = strpos( $sub_sql, $char ); } $etat = ( $etat == false ? true : false ); $this->_zonesQ[ strlen( $this->_sql ) ] = $etat; $last_false = false; foreach ( $this->_zonesQ as $index => $etat ) { if ( $etat == false ) { $last_false = $index; } else if ( $last_false != false ) { if ( $last_false == $index - 1 ) { unset( $this->_zonesQ[ $index - 1 ] ); unset( $this->_zonesQ[ $index ] ); } } } $outzones = Array(); $inzone = Array(); $last_false = - 1; $last_true = - 1; foreach ( $this->_zonesQ as $index => $etat ) { if ( $etat == true ) { $outzones[] = Array( '_a' => $last_false + 1, '_z' => $index ); $last_true = $index; } if ( $etat == false ) { $inzone[] = Array( '_a' => $last_true + 1, '_z' => $index ); $last_false = $index; } } $this->_zonesQ = Array( 'out' => $outzones, 'in' => $inzone ); } function _zonesParenthesis() { $chaine = $this->sql; $open_char = '('; $close_char = ')'; foreach ( Array( $open_char => '_a', $close_char => '_z' ) as $char => $etat ) { $sub_sql = $chaine; $pos = strpos( $sub_sql, $char ); $index = 0; while ( $pos !== false ) { $index += $pos; $sub_sql = substr( $sub_sql, $pos + 1 ); $sub_sql = ' ' . $sub_sql; if ( ! $this->_inZone( $index, $this->_zonesQ['in'] ) ) { $this->_zonesP[ $index ] = $etat; } $pos = strpos( $sub_sql, $char ); } } ksort( $this->_zonesP ); $pile = Array(); foreach ( $this->_zonesP as $index => $etat ) { if ( $etat == '_a' ) { array_push( $pile, $index ); $monkey = &$this->_zonesP_tree; foreach ( $pile as $key => $value ) { $monkey = &$monkey[ $value ]; } $monkey[ $etat ] = $index; } if ( $etat == '_z' ) { $monkey = &$this->_zonesP_tree; foreach ( $pile as $key => $value ) { $monkey = &$monkey[ $value ]; } $monkey[ $etat ] = $index; $open = array_pop( $pile ); } } $this->_zonesP_tree['_a'] = 0; $this->_zonesP_tree['_z'] = strlen( $this->_sql ); $this->_outZone( $this->_zonesP_tree ); } function _outZone( &$inzones ) { $outzones[] = Array( '_a' => $inzones['_a'], '_z' => $inzones['_z'] ); foreach ( $inzones as $i => $izone ) { if ( $i != '_a' && $i != '_z' ) { foreach ( $outzones as $o => $ozone ) { if ( $izone['_a'] < $ozone['_z'] && $izone['_z'] > $ozone['_a'] ) { if ( $izone['_a'] > $ozone['_a'] && $izone['_z'] < $ozone['_z'] ) { $outzones[] = Array( '_a' => $ozone['_a'], '_z' => $izone['_a'] - 1 ); $outzones[] = Array( '_a' => $izone['_z'] + 1, '_z' => $ozone['_z'] ); unset( $outzones[ $o ] ); } elseif ( $izone['_z'] > $ozone['_z'] ) { $outzones[] = Array( '_a' => $ozone['_a'], '_z' => $izone['_z'] - 1 ); unset( $outzones[ $o ] ); } elseif ( $izone['_a'] < $ozone['_a'] ) { $outzones[] = Array( '_a' => $izone['_z'] + 1, '_z' => $ozone['_z'] ); unset( $outzones[ $o ] ); } } } $this->_outZone( $inzones[ $i ] ); } } $inzones['out'] = $outzones; } function _inZone( $index, $zones ) { $in = false; foreach ( $zones as $zone ) { if ( $index >= $zone['_a'] && $index <= $zone['_z'] ) { $in = true; } } return $in; } function _makeBaseTree( &$_tree, $inzones ) { $sqlU = str_replace( '_', 'U', substr( $this->_sql, $inzones['_a'], $inzones['_z'] - $inzones['_a'] ) ); $this->_lexs[ $inzones['_a'] ] = str_word_count( $sqlU, 2 ); $deep = 0; $branch[ $deep ] = 'SQL'; foreach ( $this->_lexs[ $inzones['_a'] ] as $index => $candidate_rule ) { $index += $inzones['_a']; if ( in_array( $candidate_rule, $this->_list_base_rules ) && $this->_inZone( $index, $inzones['out'] ) && $this->_inZone( $index, $this->_zonesQ['out'] ) ) { while ( ! isset( $this->_base_rules[ $branch[ $deep ] ] ) ) { unset( $branch[ $deep ] ); $deep --; } while ( ! in_array( $candidate_rule, $this->_base_rules[ $branch[ $deep ] ] ) ) { unset( $branch[ $deep ] ); $deep --; if ( $deep < 0 ) { exit; } } if ( in_array( $candidate_rule, $this->_base_rules[ $branch[ $deep ] ] ) ) { $deep ++; $branch[ $deep ] = $candidate_rule; $monkey = &$_tree; foreach ( $branch as $key => $value ) { $monkey = &$monkey[ $value ]; } $monkey['_a'] = $index + strlen( $candidate_rule ); if ( isset( $oldmonkey ) ) { $oldmonkey['_z'] = $index - 1; $oldmonkey['_SQL'] = substr( $this->sql, $oldmonkey['_a'], $oldmonkey['_z'] - $oldmonkey['_a'] ); } $oldmonkey = &$monkey; } } } $oldmonkey['_z'] = $inzones['_z']; $oldmonkey['_SQL'] = substr( $this->sql, $oldmonkey['_a'], $inzones['_z'] - $oldmonkey['_a'] ); } function _makeInterBranches( $inter_rule_key, $inter_rule_val, $begin, $end, $outzones ) { $poses = Array(); $length_rule = strlen( $inter_rule_val ); $lr = 0; $min_lr = 0; if ( substr( $this->_sql, $begin, 1 ) == '(' ) { $min_lr = 1; } if ( $inter_rule_val != '' ) { $pos = strpos( $this->_sql, $inter_rule_val, $begin ); while ( $pos !== false ) { if ( $this->_inZone( $pos, $outzones ) && $this->_inZone( $pos, $this->_zonesQ['out'] ) && $pos < $end ) { $poses[] = $pos; } $sbegin = $pos + 1; $pos = strpos( $this->_sql, $inter_rule_val, $sbegin ); } } $i = 0; $last_pos = $begin; foreach ( $poses as $pos ) { if ( ! in_array( trim( substr( $this->_sql, $last_pos + $lr, $pos - $last_pos - $lr ) ), $this->_noval_rules ) ) { $lr = ( $i > 0 ? $length_rule : $min_lr ); $inter_branches[ $i . '|' . $inter_rule_key ] = Array( '_a' => $last_pos, '_z' => $pos, '_SQL' => str_repeat( ' ', $lr ) . substr( $this->sql, $last_pos + $lr, $pos - $last_pos - $lr ) ); $last_pos = $pos; $i ++; } } if ( ! in_array( trim( substr( $this->_sql, $last_pos + $lr, $end - $last_pos - $lr ) ), $this->_noval_rules ) ) { $lr = ( $i > 0 ? $length_rule : $min_lr ); $inter_branches[ $i . '|' . $inter_rule_key ] = Array( '_a' => $last_pos, '_z' => $end, '_SQL' => str_repeat( ' ', $lr ) . substr( $this->sql, $last_pos + $lr, $end - $last_pos - $lr ) ); } return $inter_branches; } function _makeInterTree( &$_tree, $outzones ) { foreach ( $_tree as $branch_rule => $sub_tree ) { if ( $branch_rule != '_a' && $branch_rule != '_z' && $branch_rule != '_SQL' ) { $found_rule = false; $pur_rule = $branch_rule; if ( array_key_exists( $pur_rule, $this->_inter_rules ) ) { $found_rule = true; } else { $rules = explode( '|', $branch_rule, 2 ); if ( is_numeric( substr( $rules[0], 0, 1 ) ) ) { $pur_rule = $rules[1]; } if ( array_key_exists( $pur_rule, $this->_inter_rules ) ) { $found_rule = true; } } if ( $found_rule ) { foreach ( $this->_inter_rules[ $pur_rule ] as $inter_rule_key => $inter_rule_val ) { if ( is_numeric( $sub_tree['_a'] ) && is_numeric( $sub_tree['_z'] ) ) { $new_branches = $this->_makeInterBranches( $inter_rule_key, $inter_rule_val, $sub_tree['_a'], $sub_tree['_z'], $outzones ); if ( count( $new_branches ) > 1 || substr( $inter_rule_key, 0, 1 ) != '!' ) { if ( is_array( $new_branches ) ) { $_tree[ $branch_rule ] += $new_branches; } if ( substr( $inter_rule_key, 0, 1 ) == '!' ) { break; } } } } } $this->_makeInterTree( $_tree[ $branch_rule ], $outzones ); } } } function _makeChildsTree( &$_tree, $parents, &$inzones ) { foreach ( $_tree as $branch => $sub_tree ) { if ( $branch == '_SQL' && count( $_tree ) < 4 ) { $child = substr( $this->_sql, $_tree['_a'], $_tree['_z'] - $_tree['_a'] ); if ( strlen( $child ) > 0 ) { $pos = strpos( $child, '(', 1 ); if ( $pos !== false ) { $pos += $_tree['_a']; if ( isset( $inzones[ $pos ] ) ) { $new_tree = Array(); $this->_makeBaseTree( $new_tree, $inzones[ $pos ] ); if ( empty( $new_tree ) ) { unset( $new_tree ); // remontée récurcive jusqu'à la dernière base rule preg_match( '/([A-Z]+)\(/', substr( $this->_sql, $_tree['_a'], $pos - $_tree['_a'] + 1 ), $agr ); if ( isset( $agr[1] ) ) { $p = $agr[1]; $this->_inter_rules[ $p ] = Array( '*' . $p => ',' ); $this->_inter_rules[ '*' . $p ] = $this->_as; } else { $n = count( $parents ); while ( $n > 0 ) { if ( in_array( $parents[ $n ], $this->_list_base_rules ) ) { $p = $parents[ $n ]; $n = 0; } $n --; } } $new_tree[ $p ]['_a'] = $inzones[ $pos ]['_a']; $new_tree[ $p ]['_z'] = $inzones[ $pos ]['_z']; $new_tree[ $p ]['_SQL'] = ' ' . substr( $this->sql, $inzones[ $pos ]['_a'] + 1, $inzones[ $pos ]['_z'] - ( $inzones[ $pos ]['_a'] + 1 ) ); $parents = Array( 'SQL' ); } else { $parents[] = $branch; } $this->_makeInterTree( $new_tree, $inzones[ $pos ]['out'] ); $this->_makeChildsTree( $new_tree, $parents, $inzones[ $pos ] ); $_tree += $new_tree; } } } } elseif ( $branch != '_SQL' && $branch != '_a' && $branch != '_z' ) { $parents[] = $branch; $this->_makeChildsTree( $_tree[ $branch ], $parents, $inzones ); } } } function _reduceTree( &$_tree ) { while ( list( $branch, $sub_tree ) = each( $_tree ) ) { if ( $branch != '_SQL' && $branch != '_a' && $branch != '_z' ) { $this->_reduceTree( $_tree[ $branch ] ); if ( array_key_exists( '_a', $_tree ) && $_tree['_a'] == $sub_tree['_a'] && $_tree['_z'] == $sub_tree['_z'] && count( $_tree ) < 5 ) { $_tree = $sub_tree; } } } } function _makeAdvanceTree( &$_tree, $parent, $grand_parent ) { foreach ( (Array) $_tree as $branch => $sub_tree ) { if ( $branch == '_SQL' ) { if ( count( $_tree ) < 4 ) { $sub_tree = str_replace( "\r", ' ', $sub_tree ); $sub_tree = str_replace( "\n", ' ', $sub_tree ); if ( substr( $parent, - 2 ) == 'AS' ) { $_parent = $grand_parent; } else { $_parent = $parent; } $rules = explode( '|', $_parent, 2 ); if ( is_numeric( substr( $rules[0], 0, 1 ) ) ) { $num_rule = $rules[0]; $pur_rule = $rules[1]; } else { $num_rule = 0; $pur_rule = $rules[0]; } if ( $num_rule == 0 || substr( $pur_rule, 0, 1 ) != '!' ) { $subs = explode( ' ', $sub_tree ); $sub_tree = ''; if ( ! isset( $this->_sheet_rules[ $pur_rule ] ) ) { $this->_sheet_rules[ $pur_rule ] = Array(); } foreach ( $subs as $sub ) { $subU = strtoupper( $sub ); if ( ! in_array( $subU, $this->_noval_rules ) ) { if ( in_array( $subU, $this->_sheet_rules[ $pur_rule ] ) ) { $_tree[ $sub ] = $sub; } else { $sub_tree .= $sub . ' '; } } } } $lsub_tree = trim( $sub_tree ); if ( in_array( $pur_rule, $this->_table_rules ) ) { $_tree['TABLE'] = $lsub_tree; } elseif ( is_numeric( $lsub_tree ) || substr( $lsub_tree, 0, 1 ) == "'" ) { $_tree['VAL'] = $lsub_tree; } else { $lsubs = explode( '.', $lsub_tree ); if ( count( $lsubs ) > 1 ) { $_tree['TABLE'] = $lsubs[0]; $_tree['FIELD'] = $lsubs[1]; } else { $_tree['FIELD'] = str_replace( ',', '', $lsub_tree ); } } } unset( $_tree['_SQL'] ); unset( $_tree['_a'] ); unset( $_tree['_z'] ); } elseif ( $branch != '_SQL' && $branch != '_a' && $branch != '_z' ) { $this->_makeAdvanceTree( $_tree[ $branch ], $branch, $parent ); } } } function make() { $this->_reformatQuery(); $this->_zonesQuote(); $this->_zonesParenthesis(); $this->_makeBaseTree( $this->_tree, $this->_zonesP_tree ); $this->_makeInterTree( $this->_tree['SQL'], $this->_zonesP_tree['out'] ); $this->_makeChildsTree( $this->_tree['SQL'], Array( 'SQL' ), $this->_zonesP_tree ); $this->_reduceTree( $this->_tree ); $this->_makeAdvanceTree( $this->_tree, 'SQL', '' ); return $this->_tree; } }