<commit_before>//
// Copyright (c) 2015 - 2017 Feisty Dog, LLC
//
// See https://github.com/feistydog/FeistyDB/blob/master/LICENSE.txt for license information
//

import Foundation

/// A queue providing concurrent execution (multiple readers and one writer) of work items on a database.
///
/// A concurrent database queue requires SQLite's [write-ahead logging](https://sqlite.org/wal.html) 
/// journaling mode to support concurrent reading and writing.
///
/// Database operations may be submitted for synchronous or asynchronous execution.
///
/// ```swift
/// let dbQueue = ConcurrentDatabaseQueue(url: URL(fileURLWithPath: "/tmp/db.sqlite"))
/// dbQueue.read { db in
///     // Read something from `db` asynchronously
/// }
/// ```
public final class ConcurrentDatabaseQueue {
	/// The underlying database
	private let database: Database
	/// The dispatch queue used for concurrent access
	private let queue = DispatchQueue(label: "com.feisty-dog.FeistyDB.ConcurrentDatabaseQueue", attributes: .concurrent)

	/// Creates a database queue for concurrent access to a database from a file.
	///
	/// - parameter url: The location of the SQLite database
	///
	/// - throws: An error if the database could not be opened or WAL journaling mode could not be set
	public init(url: URL) throws {
		self.database = try Database(url: url)

		// Set WAL mode
		let statement = try database.prepare(sql: "PRAGMA journal_mode = WAL;")
		guard let result: String = try statement.front(), result == "wal" else {
			throw DatabaseError("Could not set journaling mode to WAL")
		}
	}

	/// Creates a database queue for concurrent access to an existing database.
	///
	/// - attention: The database queue takes ownership of `database`.  The result of further use of `database` is undefined.
	///
	/// - parameter database: The database
	///
	/// - throws: An error if WAL journaling mode could not be set
	public init(database: Database) throws {
		self.database = database

		// Set WAL mode
		let statement = try database.prepare(sql: "PRAGMA journal_mode = WAL;")
		guard let result: String = try statement.front(), result == "wal" else {
			throw DatabaseError("Could not set journaling mode to WAL")
		}
	}

	/// Submits an asynchronous read operation to the database queue.
	///
	/// - parameter block: A closure performing the database operation
	/// - parameter database: A `Database` used for database access within `block`
	public func read(block: @escaping (_ database: Database) -> (Void)) {
		return queue.async {
			block(self.database)
		}
	}

	/// Performs a synchronous read operation on the database.
	///
	/// - parameter block: A closure performing the database operation
	/// - parameter database: A `Database` used for database access within `block`
	///
	/// - throws: Any error thrown in `block`
	///
	/// - returns: The value returned by `block`
	public func read_sync<T>(block: (_ database: Database) throws -> (T)) rethrows -> T {
		return try queue.sync {
			return try block(self.database)
		}<commit_msg>Added prepare(sql:) to prevent corruption of the database handle by simultaneous write operations<commit_after>//
// Copyright (c) 2015 - 2017 Feisty Dog, LLC
//
// See https://github.com/feistydog/FeistyDB/blob/master/LICENSE.txt for license information
//

import Foundation

/// A queue providing concurrent execution (multiple readers and one writer) of work items on a database.
///
/// A concurrent database queue requires SQLite's [write-ahead logging](https://sqlite.org/wal.html) 
/// journaling mode to support concurrent reading and writing.
///
/// Database operations may be submitted for synchronous or asynchronous execution.
///
/// ```swift
/// let dbQueue = ConcurrentDatabaseQueue(url: URL(fileURLWithPath: "/tmp/db.sqlite"))
/// dbQueue.read { db in
///     // Read something from `db` asynchronously
/// }
/// ```
public final class ConcurrentDatabaseQueue {
	/// The underlying database
	private let database: Database
	/// The dispatch queue used for concurrent access
	private let queue = DispatchQueue(label: "com.feisty-dog.FeistyDB.ConcurrentDatabaseQueue", attributes: .concurrent)

	/// Creates a database queue for concurrent access to a database from a file.
	///
	/// - parameter url: The location of the SQLite database
	///
	/// - throws: An error if the database could not be opened or WAL journaling mode could not be set
	public init(url: URL) throws {
		self.database = try Database(url: url)

		// Set WAL mode
		let statement = try database.prepare(sql: "PRAGMA journal_mode = WAL;")
		guard let result: String = try statement.front(), result == "wal" else {
			throw DatabaseError("Could not set journaling mode to WAL")
		}
	}

	/// Creates a database queue for concurrent access to an existing database.
	///
	/// - attention: The database queue takes ownership of `database`.  The result of further use of `database` is undefined.
	///
	/// - parameter database: The database
	///
	/// - throws: An error if WAL journaling mode could not be set
	public init(database: Database) throws {
		self.database = database

		// Set WAL mode
		let statement = try database.prepare(sql: "PRAGMA journal_mode = WAL;")
		guard let result: String = try statement.front(), result == "wal" else {
			throw DatabaseError("Could not set journaling mode to WAL")
		}
	}

	/// Returns a compiled SQL statement.
	///
	/// Use this instead of `Database.prepare(sql:)` because it
	/// serializes operations that can change the database, which
	/// may not occur on different threads.
	///
	/// - parameter sql: The SQL statement to compile
	///
	/// - throws: An error if `sql` could not be compiled
	///
	/// - returns: A compiled SQL statement
	public func prepare(sql: String) throws -> Statement {
		return try queue.sync(flags: .barrier) {
			return try database.prepare(sql: sql)
		}
	}

	/// Compiles an SQL statement and submits an asynchronous read operation to the database queue.
	///
	/// - parameter sql: The SQL statement to compile
	///
	/// - important: Only read operations may be performed in `block`.  Actions
	/// that modify the underlying database, including `Database.prepare(sql:)`,
	/// are not allowed.
	///
	/// - parameter block: A closure performing the database operation
	/// - parameter statement: A compiled version of `sql` used within `block`
	public func read(sql: String, _ block: @escaping (_ statement: Statement) -> (Void)) throws {
		let statement = try prepare(sql: sql)

		#if DEBUG
			guard statement.isReadOnly else {
				throw DatabaseError("Statement is not read-only")
			}
		#endif

		return queue.async {
			block(statement)
		}
	}

	/// Submits an asynchronous read operation to the database queue.
	///
	/// - important: Only read operations may be performed in `block`.  Actions
	/// that modify the underlying database, including `Database.prepare(sql:)`,
	/// are not allowed.
	///
	/// - parameter block: A closure performing the database operation
	/// - parameter database: A `Database` used for database access within `block`
	public func read(block: @escaping (_ database: Database) -> (Void)) {
		return queue.async {
			block(self.database)
		}
	}

	/// Performs a synchronous read operation on the database.
	///
	/// - important: Only read operations may be performed in `block`.  Actions
	/// that modify the underlying database, including `Database.prepare(sql:)`,
	/// are not allowed.
	///
	/// - parameter block: A closure performing the database operation
	/// - parameter database: A `Database` used for database access within `block`
	///
	/// - throws: Any error thrown in `block`
	///
	/// - returns: The value returned by `block`
	public func read_sync<T>(block: (_ database: Database) throws -> (T)) rethrows -> T {
		return try queue.sync {
			return try block(self.database)
		}
{'fraction_non_alphanumeric': 0.11846153846153847, 'fraction_numerical': 0.0020512820512820513, 'mean_word_length': 3.194086021505376, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 13, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1066797', 'n_tokens_mistral': 2238, 'n_tokens_neox': 2094, 'n_words': 873}