Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/php-wasm/universal/src/lib/fs-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class FSHelpers {
static writeFile(
FS: Emscripten.RootFS,
path: string,
data: string | Uint8Array
data: string | Uint8Array | Buffer
) {
FS.writeFile(path, data);
}
Expand Down
15 changes: 7 additions & 8 deletions packages/php-wasm/universal/src/lib/php.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export class PHP implements Disposable {
// Always enable the file cache.
'opcache.file_cache_only = 1',
'opcache.file_cache_consistency_checks = 1',
]
]
: [];

/*if (
Expand Down Expand Up @@ -508,9 +508,8 @@ export class PHP implements Disposable {
*/
async run(request: PHPRunOptions): Promise<PHPResponse> {
const streamedResponse = await this.runStream(request);
const syncResponse = await PHPResponse.fromStreamedResponse(
streamedResponse
);
const syncResponse =
await PHPResponse.fromStreamedResponse(streamedResponse);

if (syncResponse.exitCode !== 0) {
// Legacy run() behavior: throw if PHP exited with a non-zero exit code.
Expand Down Expand Up @@ -1189,7 +1188,7 @@ export class PHP implements Disposable {
* @param path - The file path to write to.
* @param data - The data to write to the file.
*/
writeFile(path: string, data: string | Uint8Array) {
writeFile(path: string, data: string | Uint8Array | Buffer) {
const result = FSHelpers.writeFile(
this[__private__dont__use].FS,
path,
Expand Down Expand Up @@ -1754,9 +1753,9 @@ const getNodeType = (fs: Emscripten.FileSystemInstance, path: string) => {
return 'contents' in target.node
? 'memfs'
: /**
* Could be NODEFS, PROXYFS, etc.
*/
'not-memfs';
* Could be NODEFS, PROXYFS, etc.
*/
'not-memfs';
} catch {
return 'missing';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?php

/**
* Naively splits an SQL string into a sequence of queries. It
* streams the data so you can process very large chunks of SQL
* without running out of memory.
*
* This class is **naive** because it doesn't understand what a
* valid query is. The lexer does not provide a way to distinguish
* between a syntax error and an incomplete input yet. Lacking this
* information, we assume that no SQL query is larger than 2MB and,
* failing to extract a query from a 2MB buffer, we fail. This heuristic
* is often sufficient, but may fail in pathological cases.
*
* Usage:
*
* $stream = new WP_MySQL_Naive_Query_Stream();
* $stream->append_sql( 'SELECT id FROM users; SELECT * FROM posts;' );
* while ( $stream->next_query() ) {
* $sql_string = $stream->get_query();
* // Process the query.
* }
* $stream->append_sql( 'CREATE TABLE users (id INT, name VARCHAR(255));' );
* while ( $stream->next_query() ) {
* $sql_string = $stream->get_query();
* // Process the query.
* }
* $stream->mark_input_complete();
* $stream->next_query(); // returns false
*/
class WP_MySQL_Naive_Query_Stream {

private $sql_buffer = '';
private $input_complete = false;
private $state = true;
private $last_query = false;

const STATE_QUERY = 'valid';
const STATE_SYNTAX_ERROR = 'syntax_error';
const STATE_PAUSED_ON_INCOMPLETE_INPUT = 'paused_on_incomplete_input';
const STATE_FINISHED = 'finished';

/**
* The maximum size of the buffer to store the SQL input. We don't
* have enough information from the lexer to distinguish between
* an incomplete input and a syntax error so we use a heuristic –
* if we've accumulated more than this amount of SQL input, we assume
* it's a syntax error. That's why this class is called a "naive" query
* stream.
*/
const MAX_SQL_BUFFER_SIZE = 1024 * 1024 * 15;

public function __construct() {}

public function append_sql( string $sql ) {
if($this->input_complete) {
return false;
}
$this->sql_buffer .= $sql;
$this->state = self::STATE_QUERY;
return true;
}

public function is_paused_on_incomplete_input(): bool {
return $this->state === self::STATE_PAUSED_ON_INCOMPLETE_INPUT;
}

public function mark_input_complete() {
$this->input_complete = true;
}

public function next_query() {
$this->last_query = false;
if($this->state === self::STATE_PAUSED_ON_INCOMPLETE_INPUT) {
return false;
}

$result = $this->do_next_query();
if(!$result && strlen($this->sql_buffer) > self::MAX_SQL_BUFFER_SIZE) {
$this->state = self::STATE_SYNTAX_ERROR;
return false;
}
return $result;
}

private function do_next_query() {
$query = [];
$lexer = new WP_MySQL_Lexer( $this->sql_buffer );
while ( $lexer->next_token() ) {
$token = $lexer->get_token();
$query[] = $token;
if ( $token->id === WP_MySQL_Lexer::SEMICOLON_SYMBOL ) {
// Got a complete query!
break;
}
}

// @TODO: expose this method from the lexer
// if($lexer->get_state() === WP_MySQL_Lexer::STATE_SYNTAX_ERROR) {
// return false;
// }

if(!count($query)) {
if ( $this->input_complete ) {
$this->state = self::STATE_FINISHED;
} else {
$this->state = self::STATE_PAUSED_ON_INCOMPLETE_INPUT;
}
return false;
}

// The last token either needs to end with a semicolon, or be the
// last token in the input.
$last_token = $query[count($query) - 1];
if (
$last_token->id !== WP_MySQL_Lexer::SEMICOLON_SYMBOL &&
! $this->input_complete
) {
$this->state = self::STATE_PAUSED_ON_INCOMPLETE_INPUT;
return false;
}

// See if the query has any meaningful tokens. We don't want to return
// to give the caller a comment disguised as a query.
$has_meaningful_tokens = false;
foreach($query as $token) {
if (
$token->id !== WP_MySQL_Lexer::WHITESPACE &&
$token->id !== WP_MySQL_Lexer::COMMENT &&
$token->id !== WP_MySQL_Lexer::MYSQL_COMMENT_START &&
$token->id !== WP_MySQL_Lexer::MYSQL_COMMENT_END &&
$token->id !== WP_MySQL_Lexer::EOF
) {
$has_meaningful_tokens = true;
break;
}
}
if(!$has_meaningful_tokens) {
if ( $this->input_complete ) {
$this->state = self::STATE_FINISHED;
} else {
$this->state = self::STATE_PAUSED_ON_INCOMPLETE_INPUT;
}
return false;
}

// Remove the query from the input buffer and return it.
$last_byte = $last_token->start + $last_token->length;
$query = substr($this->sql_buffer, 0, $last_byte);
$this->sql_buffer = substr($this->sql_buffer, $last_byte);
$this->last_query = $query;
$this->state = self::STATE_QUERY;
return true;
}

public function get_query() {
return $this->last_query;
}

public function get_state() {
return $this->state;
}

}
Loading
Loading