Base de Datos macOS Objective-C - Ejemplos

Ejemplos de base de datos macOS Objective-C incluyendo operaciones CRUD SQLite, consultas, transacciones y gestión de conexiones

💻 Operaciones CRUD SQLite objectivec

🟡 intermediate ⭐⭐⭐

Realizar operaciones de creación, lectura, actualización y eliminación con base de datos SQLite

⏱️ 30 min 🏷️ objectivec, macos, database, sqlite
Prerequisites: Objective-C basics, SQLite3 framework
// macOS Objective-C SQLite CRUD Examples
// Using SQLite3 framework

#import <Foundation/Foundation.h>
#import <sqlite3.h>

// MARK: - 1. Database Connection

@interface SQLiteManager : NSObject

@property (nonatomic, assign) sqlite3 *database;
@property (nonatomic, strong) NSString *databasePath;

- (BOOL)openDatabase:(NSString *)path;
- (void)closeDatabase;

@end

@implementation SQLiteManager

- (BOOL)openDatabase:(NSString *)path {
    self.databasePath = path;

    int result = sqlite3_open([path UTF8String], &_database);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to open database: %s", sqlite3_errmsg(_database));
        return NO;
    }

    NSLog(@"Database opened: %@", path);
    return YES;
}

- (void)closeDatabase {
    if (_database) {
        sqlite3_close(_database);
        _database = NULL;
        NSLog(@"Database closed");
    }
}

- (void)dealloc {
    [self closeDatabase];
}

@end

// MARK: - 2. Create Table

@interface TableCreator : SQLiteManager

- (BOOL)createUsersTable;
- (BOOL)createProductsTable;

@end

@implementation TableCreator

- (BOOL)createUsersTable {
    NSString *sql = @`
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            email TEXT UNIQUE NOT NULL,
            age INTEGER,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        );
    `;

    char *error = NULL;
    int result = sqlite3_exec(_database, [sql UTF8String], NULL, NULL, &error);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to create users table: %s", error);
        sqlite3_free(error);
        return NO;
    }

    NSLog(@"Users table created successfully");
    return YES;
}

- (BOOL)createProductsTable {
    NSString *sql = @`
        CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            price REAL NOT NULL,
            quantity INTEGER DEFAULT 0,
            category TEXT
        );
    `;

    char *error = NULL;
    int result = sqlite3_exec(_database, [sql UTF8String], NULL, NULL, &error);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to create products table: %s", error);
        sqlite3_free(error);
        return NO;
    }

    NSLog(@"Products table created successfully");
    return YES;
}

@end

// MARK: - 3. Insert Operations

@interface DataInserter : SQLiteManager

- (BOOL)insertUser:(NSString *)name
              email:(NSString *)email
                age:(NSInteger)age;
- (NSInteger)insertProduct:(NSString *)name
                   price:(double)price
                 quantity:(NSInteger)quantity
                 category:(NSString *)category;

@end

@implementation DataInserter

- (BOOL)insertUser:(NSString *)name
              email:(NSString *)email
                age:(NSInteger)age {
    NSString *sql = @"INSERT INTO users (name, email, age) VALUES (?, ?, ?);";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare insert statement: %s", sqlite3_errmsg(_database));
        return NO;
    }

    // Bind parameters
    sqlite3_bind_text(statement, 1, [name UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(statement, 2, [email UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_int(statement, 3, (int)age);

    result = sqlite3_step(statement);

    if (result != SQLITE_DONE) {
        NSLog(@"Failed to insert user: %s", sqlite3_errmsg(_database));
        sqlite3_finalize(statement);
        return NO;
    }

    NSInteger userId = (NSInteger)sqlite3_last_insert_rowid(_database);
    NSLog(@"User inserted with ID: %ld", (long)userId);

    sqlite3_finalize(statement);
    return YES;
}

- (NSInteger)insertProduct:(NSString *)name
                   price:(double)price
                 quantity:(NSInteger)quantity
                 category:(NSString *)category {
    NSString *sql = @"INSERT INTO products (name, price, quantity, category) VALUES (?, ?, ?, ?);";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare statement: %s", sqlite3_errmsg(_database));
        return -1;
    }

    sqlite3_bind_text(statement, 1, [name UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_double(statement, 2, price);
    sqlite3_bind_int(statement, 3, (int)quantity);
    sqlite3_bind_text(statement, 4, [category UTF8String], -1, SQLITE_TRANSIENT);

    result = sqlite3_step(statement);

    if (result != SQLITE_DONE) {
        NSLog(@"Failed to insert product: %s", sqlite3_errmsg(_database));
        sqlite3_finalize(statement);
        return -1;
    }

    NSInteger productId = (NSInteger)sqlite3_last_insert_rowid(_database);
    NSLog(@"Product inserted with ID: %ld", (long)productId);

    sqlite3_finalize(statement);
    return productId;
}

@end

// MARK: - 4. Select Operations

@interface DataSelector : SQLiteManager

- (NSArray *)getAllUsers;
- (NSDictionary *)getUserById:(NSInteger)userId;
- (NSArray *)getUsersByAge:(NSInteger)minAge;
- (NSArray *)getProductsByCategory:(NSString *)category;

@end

@implementation DataSelector

- (NSArray *)getAllUsers {
    NSString *sql = @"SELECT id, name, email, age, created_at FROM users;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare select: %s", sqlite3_errmsg(_database));
        return @[];
    }

    NSMutableArray *users = [NSMutableArray array];

    while (sqlite3_step(statement) == SQLITE_ROW) {
        NSMutableDictionary *user = [NSMutableDictionary dictionary];

        [user setObject:@(sqlite3_column_int(statement, 0)) forKey:@"id"];

        const char *name = (const char *)sqlite3_column_text(statement, 1);
        if (name) [user setObject:[NSString stringWithUTF8String:name] forKey:@"name"];

        const char *email = (const char *)sqlite3_column_text(statement, 2);
        if (email) [user setObject:[NSString stringWithUTF8String:email] forKey:@"email"];

        [user setObject:@(sqlite3_column_int(statement, 3)) forKey:@"age"];

        [users addObject:[user copy]];
    }

    sqlite3_finalize(statement);

    NSLog(@"Retrieved %lu users", (unsigned long)users.count);
    return [users copy];
}

- (NSDictionary *)getUserById:(NSInteger)userId {
    NSString *sql = @"SELECT id, name, email, age, created_at FROM users WHERE id = ?;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare statement: %s", sqlite3_errmsg(_database));
        return nil;
    }

    sqlite3_bind_int(statement, 1, (int)userId);

    if (sqlite3_step(statement) != SQLITE_ROW) {
        NSLog(@"User not found");
        sqlite3_finalize(statement);
        return nil;
    }

    NSMutableDictionary *user = [NSMutableDictionary dictionary];

    [user setObject:@(sqlite3_column_int(statement, 0)) forKey:@"id"];

    const char *name = (const char *)sqlite3_column_text(statement, 1);
    if (name) [user setObject:[NSString stringWithUTF8String:name] forKey:@"name"];

    const char *email = (const char *)sqlite3_column_text(statement, 2);
    if (email) [user setObject:[NSString stringWithUTF8String:email] forKey:@"email"];

    [user setObject:@(sqlite3_column_int(statement, 3)) forKey:@"age"];

    sqlite3_finalize(statement);

    NSLog(@"Retrieved user: %@", user);
    return [user copy];
}

- (NSArray *)getUsersByAge:(NSInteger)minAge {
    NSString *sql = @"SELECT id, name, email, age FROM users WHERE age >= ? ORDER BY age ASC;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare statement: %s", sqlite3_errmsg(_database));
        return @[];
    }

    sqlite3_bind_int(statement, 1, (int)minAge);

    NSMutableArray *users = [NSMutableArray array];

    while (sqlite3_step(statement) == SQLITE_ROW) {
        NSMutableDictionary *user = [NSMutableDictionary dictionary];

        [user setObject:@(sqlite3_column_int(statement, 0)) forKey:@"id"];

        const char *name = (const char *)sqlite3_column_text(statement, 1);
        if (name) [user setObject:[NSString stringWithUTF8String:name] forKey:@"name"];

        const char *email = (const char *)sqlite3_column_text(statement, 2);
        if (email) [user setObject:[NSString stringWithUTF8String:email] forKey:@"email"];

        [user setObject:@(sqlite3_column_int(statement, 3)) forKey:@"age"];

        [users addObject:[user copy]];
    }

    sqlite3_finalize(statement);

    NSLog(@"Retrieved %lu users with age >= %ld", (unsigned long)users.count, (long)minAge);
    return [users copy];
}

- (NSArray *)getProductsByCategory:(NSString *)category {
    NSString *sql = @"SELECT id, name, price, quantity, category FROM products WHERE category = ?;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare statement: %s", sqlite3_errmsg(_database));
        return @[];
    }

    sqlite3_bind_text(statement, 1, [category UTF8String], -1, SQLITE_TRANSIENT);

    NSMutableArray *products = [NSMutableArray array];

    while (sqlite3_step(statement) == SQLITE_ROW) {
        NSMutableDictionary *product = [NSMutableDictionary dictionary];

        [product setObject:@(sqlite3_column_int(statement, 0)) forKey:@"id"];

        const char *name = (const char *)sqlite3_column_text(statement, 1);
        if (name) [product setObject:[NSString stringWithUTF8String:name] forKey:@"name"];

        [product setObject:@(sqlite3_column_double(statement, 2)) forKey:@"price"];
        [product setObject:@(sqlite3_column_int(statement, 3)) forKey:@"quantity"];

        const char *cat = (const char *)sqlite3_column_text(statement, 4);
        if (cat) [product setObject:[NSString stringWithUTF8String:cat] forKey:@"category"];

        [products addObject:[product copy]];
    }

    sqlite3_finalize(statement);

    NSLog(@"Retrieved %lu products in category %@", (unsigned long)products.count, category);
    return [products copy];
}

@end

// MARK: - 5. Update Operations

@interface DataUpdater : SQLiteManager

- (BOOL)updateUser:(NSInteger)userId
                setName:(NSString *)name;
- (BOOL)updateUserAge:(NSInteger)userId age:(NSInteger)age;
- (BOOL)updateProductQuantity:(NSInteger)productId quantity:(NSInteger)quantity;

@end

@implementation DataUpdater

- (BOOL)updateUser:(NSInteger)userId
                setName:(NSString *)name {
    NSString *sql = @"UPDATE users SET name = ? WHERE id = ?;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare update: %s", sqlite3_errmsg(_database));
        return NO;
    }

    sqlite3_bind_text(statement, 1, [name UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_int(statement, 2, (int)userId);

    result = sqlite3_step(statement);

    if (result != SQLITE_DONE) {
        NSLog(@"Failed to update user: %s", sqlite3_errmsg(_database));
        sqlite3_finalize(statement);
        return NO;
    }

    NSLog(@"Updated user %ld name to %@", (long)userId, name);

    sqlite3_finalize(statement);
    return YES;
}

- (BOOL)updateUserAge:(NSInteger)userId age:(NSInteger)age {
    NSString *sql = @"UPDATE users SET age = ? WHERE id = ?;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare update: %s", sqlite3_errmsg(_database));
        return NO;
    }

    sqlite3_bind_int(statement, 1, (int)age);
    sqlite3_bind_int(statement, 2, (int)userId);

    result = sqlite3_step(statement);

    if (result != SQLITE_DONE) {
        NSLog(@"Failed to update age: %s", sqlite3_errmsg(_database));
        sqlite3_finalize(statement);
        return NO;
    }

    NSLog(@"Updated user %ld age to %ld", (long)userId, (long)age);

    sqlite3_finalize(statement);
    return YES;
}

- (BOOL)updateProductQuantity:(NSInteger)productId quantity:(NSInteger)quantity {
    NSString *sql = @"UPDATE products SET quantity = ? WHERE id = ?;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare update: %s", sqlite3_errmsg(_database));
        return NO;
    }

    sqlite3_bind_int(statement, 1, (int)quantity);
    sqlite3_bind_int(statement, 2, (int)productId);

    result = sqlite3_step(statement);

    if (result != SQLITE_DONE) {
        NSLog(@"Failed to update quantity: %s", sqlite3_errmsg(_database));
        sqlite3_finalize(statement);
        return NO;
    }

    NSLog(@"Updated product %ld quantity to %ld", (long)productId, (long)quantity);

    sqlite3_finalize(statement);
    return YES;
}

@end

// MARK: - 6. Delete Operations

@interface DataDeleter : SQLiteManager

- (BOOL)deleteUser:(NSInteger)userId;
- (BOOL)deleteProduct:(NSInteger)productId;
- (NSInteger)deleteUsersByAge:(NSInteger)maxAge;

@end

@implementation DataDeleter

- (BOOL)deleteUser:(NSInteger)userId {
    NSString *sql = @"DELETE FROM users WHERE id = ?;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare delete: %s", sqlite3_errmsg(_database));
        return NO;
    }

    sqlite3_bind_int(statement, 1, (int)userId);

    result = sqlite3_step(statement);

    if (result != SQLITE_DONE) {
        NSLog(@"Failed to delete user: %s", sqlite3_errmsg(_database));
        sqlite3_finalize(statement);
        return NO;
    }

    NSLog(@"Deleted user %ld", (long)userId);

    sqlite3_finalize(statement);
    return YES;
}

- (BOOL)deleteProduct:(NSInteger)productId {
    NSString *sql = @"DELETE FROM products WHERE id = ?;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare delete: %s", sqlite3_errmsg(_database));
        return NO;
    }

    sqlite3_bind_int(statement, 1, (int)productId);

    result = sqlite3_step(statement);

    if (result != SQLITE_DONE) {
        NSLog(@"Failed to delete product: %s", sqlite3_errmsg(_database));
        sqlite3_finalize(statement);
        return NO;
    }

    NSLog(@"Deleted product %ld", (long)productId);

    sqlite3_finalize(statement);
    return YES;
}

- (NSInteger)deleteUsersByAge:(NSInteger)maxAge {
    NSString *sql = @"DELETE FROM users WHERE age > ?;";

    sqlite3_stmt *statement;
    int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to prepare delete: %s", sqlite3_errmsg(_database));
        return 0;
    }

    sqlite3_bind_int(statement, 1, (int)maxAge);

    result = sqlite3_step(statement);

    if (result != SQLITE_DONE) {
        NSLog(@"Failed to delete users: %s", sqlite3_errmsg(_database));
        sqlite3_finalize(statement);
        return 0;
    }

    NSInteger deletedCount = sqlite3_changes(_database);
    NSLog(@"Deleted %ld users with age > %ld", (long)deletedCount, (long)maxAge);

    sqlite3_finalize(statement);
    return deletedCount;
}

@end

// MARK: - Main Demonstration

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"=== macOS Objective-C SQLite CRUD Examples ===\n");

        SQLiteManager *manager = [[SQLiteManager alloc] init];

        NSString *dbPath = @"/tmp/test_database.sqlite";

        // Create and open database
        if (![manager openDatabase:dbPath]) {
            NSLog(@"Failed to open database");
            return 1;
        }

        // Create tables
        TableCreator *creator = (TableCreator *)manager;
        [creator createUsersTable];
        [creator createProductsTable];

        // Insert data
        DataInserter *inserter = (DataInserter *)manager;

        [inserter insertUser:@"Alice"
                        email:@"[email protected]"
                          age:30];

        [inserter insertUser:@"Bob"
                        email:@"[email protected]"
                          age:25];

        [inserter insertUser:@"Charlie"
                        email:@"[email protected]"
                          age:35];

        NSInteger productId = [inserter insertProduct:@"Laptop"
                                            price:999.99
                                          quantity:10
                                          category:@"Electronics"];

        [inserter insertProduct:@"Mouse"
                            price:25.50
                          quantity:50
                        category:@"Electronics"];

        [inserter insertProduct:@"Desk"
                            price:450.00
                          quantity:5
                        category:@"Furniture"];

        // Select data
        DataSelector *selector = (DataSelector *)manager;

        NSLog(@"\n--- All Users ---");
        NSArray *users = [selector getAllUsers];
        for (NSDictionary *user in users) {
            NSLog(@"  User: %@ - %@", user[@"name"], user[@"email"]);
        }

        NSLog(@"\n--- User by ID ---");
        NSDictionary *user = [selector getUserById:1];
        NSLog(@"User: %@", user);

        NSLog(@"\n--- Users Age >= 30 ---");
        NSArray *olderUsers = [selector getUsersByAge:30];
        for (NSDictionary *u in olderUsers) {
            NSLog(@"  %@ (%ld years old)", u[@"name"], (long)[u[@"age"] integerValue]);
        }

        NSLog(@"\n--- Products in Electronics ---");
        NSArray *products = [selector getProductsByCategory:@"Electronics"];
        for (NSDictionary *p in products) {
            NSLog(@"  %@ - $%.2f (x%ld)", p[@"name"], [p[@"price"] doubleValue], (long)[p[@"quantity"] integerValue]);
        }

        // Update data
        DataUpdater *updater = (DataUpdater *)manager;

        [updater updateUserAge:1 age:31];
        [updater updateProductQuantity:productId quantity:8];

        // Delete data
        DataDeleter *deleter = (DataDeleter *)manager;

        [deleter deleteUser:2];
        [deleter deleteProduct:productId];

        NSInteger deleted = [deleter deleteUsersByAge:100];
        NSLog(@"\nDeleted %ld users older than 100", (long)deleted);

        // Close database
        [manager closeDatabase];

        NSLog(@"\n=== SQLite CRUD Examples Completed ===");
    }

    return 0;
}

💻 Transacciones y Operaciones por Lotes objectivec

🔴 complex ⭐⭐⭐⭐

Implementar transacciones de base de datos, inserciones por lotes y reversión de errores

⏱️ 35 min 🏷️ objectivec, macos, database, sqlite, transactions
Prerequisites: Advanced Objective-C, SQLite3 framework
// macOS Objective-C SQLite Transactions Examples
// Using SQLite3 framework

#import <Foundation/Foundation.h>
#import <sqlite3.h>

// MARK: - 1. Transaction Manager

@interface TransactionManager : NSObject

@property (nonatomic, assign) sqlite3 *database;

- (instancetype)initWithDatabase:(sqlite3 *)database;
- (BOOL)beginTransaction;
- (BOOL)commitTransaction;
- (BOOL)rollbackTransaction;
- (BOOL)performTransaction:(BOOL (^)(void))block;

@end

@implementation TransactionManager

- (instancetype)initWithDatabase:(sqlite3 *)database {
    self = [super init];

    if (self) {
        _database = database;
    }

    return self;
}

- (BOOL)beginTransaction {
    char *error = NULL;
    int result = sqlite3_exec(_database, "BEGIN TRANSACTION;", NULL, NULL, &error);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to begin transaction: %s", error);
        sqlite3_free(error);
        return NO;
    }

    NSLog(@"Transaction started");
    return YES;
}

- (BOOL)commitTransaction {
    char *error = NULL;
    int result = sqlite3_exec(_database, "COMMIT;", NULL, NULL, &error);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to commit: %s", error);
        sqlite3_free(error);
        return NO;
    }

    NSLog(@"Transaction committed");
    return YES;
}

- (BOOL)rollbackTransaction {
    char *error = NULL;
    int result = sqlite3_exec(_database, "ROLLBACK;", NULL, NULL, &error);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to rollback: %s", error);
        sqlite3_free(error);
        return NO;
    }

    NSLog(@"Transaction rolled back");
    return YES;
}

- (BOOL)performTransaction:(BOOL (^)(void))block {
    if (![self beginTransaction]) {
        return NO;
    }

    BOOL success = block();

    if (success) {
        return [self commitTransaction];
    } else {
        return [self rollbackTransaction];
    }
}

@end

// MARK: - 2. Batch Insert

@interface BatchInserter : NSObject

@property (nonatomic, assign) sqlite3 *database;

- (instancetype)initWithDatabase:(sqlite3 *)database;
- (BOOL)batchInsertUsers:(NSArray *)users;
- (BOOL)batchInsertProducts:(NSArray *)products;

@end

@implementation BatchInserter

- (instancetype)initWithDatabase:(sqlite3 *)database {
    self = [super init];

    if (self) {
        _database = database;
    }

    return self;
}

- (BOOL)batchInsertUsers:(NSArray *)users {
    TransactionManager *manager = [[TransactionManager alloc] initWithDatabase:_database];

    return [manager performTransaction:^{
        NSString *sql = @"INSERT INTO users (name, email, age) VALUES (?, ?, ?);";

        sqlite3_stmt *statement;
        int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

        if (result != SQLITE_OK) {
            NSLog(@"Failed to prepare statement: %s", sqlite3_errmsg(_database));
            return NO;
        }

        NSInteger successCount = 0;

        for (NSDictionary *user in users) {
            sqlite3_bind_text(statement, 1, [user[@"name"] UTF8String], -1, SQLITE_TRANSIENT);
            sqlite3_bind_text(statement, 2, [user[@"email"] UTF8String], -1, SQLITE_TRANSIENT);
            sqlite3_bind_int(statement, 3, [user[@"age"] intValue]);

            result = sqlite3_step(statement);

            if (result == SQLITE_DONE) {
                successCount++;
            } else {
                NSLog(@"Failed to insert user: %s", sqlite3_errmsg(_database));
            }

            sqlite3_reset(statement);
        }

        sqlite3_finalize(statement);

        NSLog(@"Batch insert completed: %ld/%lu users inserted", (long)successCount, (unsigned long)users.count);

        return successCount == users.count;
    }];
}

- (BOOL)batchInsertProducts:(NSArray *)products {
    TransactionManager *manager = [[TransactionManager alloc] initWithDatabase:_database];

    return [manager performTransaction:^{
        NSString *sql = @"INSERT INTO products (name, price, quantity, category) VALUES (?, ?, ?, ?);";

        sqlite3_stmt *statement;
        int result = sqlite3_prepare_v2(_database, [sql UTF8String], -1, &statement, NULL);

        if (result != SQLITE_OK) {
            NSLog(@"Failed to prepare statement: %s", sqlite3_errmsg(_database));
            return NO;
        }

        NSInteger successCount = 0;

        for (NSDictionary *product in products) {
            sqlite3_bind_text(statement, 1, [product[@"name"] UTF8String], -1, SQLITE_TRANSIENT);
            sqlite3_bind_double(statement, 2, [product[@"price"] doubleValue]);
            sqlite3_bind_int(statement, 3, [product[@"quantity"] intValue]);
            sqlite3_bind_text(statement, 4, [product[@"category"] UTF8String], -1, SQLITE_TRANSIENT);

            result = sqlite3_step(statement);

            if (result == SQLITE_DONE) {
                successCount++;
            } else {
                NSLog(@"Failed to insert product: %s", sqlite3_errmsg(_database));
            }

            sqlite3_reset(statement);
        }

        sqlite3_finalize(statement);

        NSLog(@"Batch insert completed: %ld/%lu products inserted", (long)successCount, (unsigned long)products.count);

        return successCount == products.count;
    }];
}

@end

// MARK: - 3. Savepoint

@interface SavepointManager : NSObject

@property (nonatomic, assign) sqlite3 *database;

- (instancetype)initWithDatabase:(sqlite3 *)database;
- (BOOL)createSavepoint:(NSString *)name;
- (BOOL)releaseSavepoint:(NSString *)name;
- (BOOL)rollbackToSavepoint:(NSString *)name;

@end

@implementation SavepointManager

- (instancetype)initWithDatabase:(sqlite3 *)database {
    self = [super init];

    if (self) {
        _database = database;
    }

    return self;
}

- (BOOL)createSavepoint:(NSString *)name {
    NSString *sql = [NSString stringWithFormat:@"SAVEPOINT %@;", name];

    char *error = NULL;
    int result = sqlite3_exec(_database, [sql UTF8String], NULL, NULL, &error);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to create savepoint: %s", error);
        sqlite3_free(error);
        return NO;
    }

    NSLog(@"Savepoint '%@' created", name);
    return YES;
}

- (BOOL)releaseSavepoint:(NSString *)name {
    NSString *sql = [NSString stringWithFormat:@"RELEASE SAVEPOINT %@;", name];

    char *error = NULL;
    int result = sqlite3_exec(_database, [sql UTF8String], NULL, NULL, &error);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to release savepoint: %s", error);
        sqlite3_free(error);
        return NO;
    }

    NSLog(@"Savepoint '%@' released", name);
    return YES;
}

- (BOOL)rollbackToSavepoint:(NSString *)name {
    NSString *sql = [NSString stringWithFormat:@"ROLLBACK TO SAVEPOINT %@;", name];

    char *error = NULL;
    int result = sqlite3_exec(_database, [sql UTF8String], NULL, NULL, &error);

    if (result != SQLITE_OK) {
        NSLog(@"Failed to rollback to savepoint: %s", error);
        sqlite3_free(error);
        return NO;
    }

    NSLog(@"Rolled back to savepoint '%@'", name);
    return YES;
}

@end

// MARK: - 4. Atomic Updates

@interface AtomicUpdater : NSObject

@property (nonatomic, assign) sqlite3 *database;

- (instancetype)initWithDatabase:(sqlite3 *)database;
- (BOOL)transferAmount:(double)amount
          fromAccount:(NSInteger)fromId
            toAccount:(NSInteger)toId;

@end

@implementation AtomicUpdater

- (instancetype)initWithDatabase:(sqlite3 *)database {
    self = [super init];

    if (self) {
        _database = database;
    }

    return self;
}

- (BOOL)transferAmount:(double)amount
          fromAccount:(NSInteger)fromId
            toAccount:(NSInteger)toId {
    TransactionManager *manager = [[TransactionManager alloc] initWithDatabase:_database];

    return [manager performTransaction:^{
        // Check balance
        NSString *checkSql = @"SELECT balance FROM accounts WHERE id = ?;";
        sqlite3_stmt *checkStatement;
        double balance = 0;

        if (sqlite3_prepare_v2(_database, [checkSql UTF8String], -1, &checkStatement, NULL) == SQLITE_OK) {
            sqlite3_bind_int(checkStatement, 1, (int)fromId);

            if (sqlite3_step(checkStatement) == SQLITE_ROW) {
                balance = sqlite3_column_double(checkStatement, 0);
            }

            sqlite3_finalize(checkStatement);
        }

        if (balance < amount) {
            NSLog(@"Insufficient funds in account %ld", (long)fromId);
            return NO;
        }

        // Deduct from sender
        NSString *deductSql = @"UPDATE accounts SET balance = balance - ? WHERE id = ?;";
        sqlite3_stmt *deductStatement;

        if (sqlite3_prepare_v2(_database, [deductSql UTF8String], -1, &deductStatement, NULL) != SQLITE_OK) {
            NSLog(@"Failed to prepare deduct statement");
            return NO;
        }

        sqlite3_bind_double(deductStatement, 1, amount);
        sqlite3_bind_int(deductStatement, 2, (int)fromId);

        if (sqlite3_step(deductStatement) != SQLITE_DONE) {
            NSLog(@"Failed to deduct from account %ld", (long)fromId);
            sqlite3_finalize(deductStatement);
            return NO;
        }

        sqlite3_finalize(deductStatement);

        // Add to receiver
        NSString *addSql = @"UPDATE accounts SET balance = balance + ? WHERE id = ?;";
        sqlite3_stmt *addStatement;

        if (sqlite3_prepare_v2(_database, [addSql UTF8String], -1, &addStatement, NULL) != SQLITE_OK) {
            NSLog(@"Failed to prepare add statement");
            return NO;
        }

        sqlite3_bind_double(addStatement, 1, amount);
        sqlite3_bind_int(addStatement, 2, (int)toId);

        if (sqlite3_step(addStatement) != SQLITE_DONE) {
            NSLog(@"Failed to add to account %ld", (long)toId);
            sqlite3_finalize(addStatement);
            return NO;
        }

        sqlite3_finalize(addStatement);

        NSLog(@"Transferred %.2f from account %ld to %ld", amount, (long)fromId, (long)toId);
        return YES;
    }];
}

@end

// MARK: - Main Demonstration

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"=== macOS Objective-C SQLite Transactions Examples ===\n");

        sqlite3 *database;
        NSString *dbPath = @"/tmp/test_trans.sqlite";

        if (sqlite3_open([dbPath UTF8String], &database) != SQLITE_OK) {
            NSLog(@"Failed to open database");
            return 1;
        }

        // Create tables
        char *error = NULL;

        const char *createUsersSQL =             "CREATE TABLE IF NOT EXISTS users ("
            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
            "name TEXT NOT NULL,"
            "email TEXT UNIQUE NOT NULL,"
            "age INTEGER"
            ");";

        const char *createAccountsSQL =             "CREATE TABLE IF NOT EXISTS accounts ("
            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
            "balance REAL NOT NULL DEFAULT 0"
            ");";

        sqlite3_exec(database, createUsersSQL, NULL, NULL, &error);
        sqlite3_exec(database, createAccountsSQL, NULL, NULL, &error);

        // Initialize accounts
        sqlite3_exec(database, "INSERT INTO accounts (balance) VALUES (1000.00);", NULL, NULL, NULL);
        sqlite3_exec(database, "INSERT INTO accounts (balance) VALUES (500.00);", NULL, NULL, NULL);

        NSLog(@"Database initialized");

        // 1. Transaction manager
        NSLog(@"\n--- Transaction Manager ---");

        TransactionManager *txManager = [[TransactionManager alloc] initWithDatabase:database];

        [txManager performTransaction:^{
            sqlite3_exec(database, "INSERT INTO users (name, email, age) VALUES ('Alice', '[email protected]', 30);", NULL, NULL, NULL);
            sqlite3_exec(database, "INSERT INTO users (name, email, age) VALUES ('Bob', '[email protected]', 25);", NULL, NULL, NULL);

            NSLog(@"Inserted users in transaction");
            return YES;
        }];

        // 2. Batch insert
        NSLog(@"\n--- Batch Insert ---");

        BatchInserter *batchInserter = [[BatchInserter alloc] initWithDatabase:database];

        NSArray *newUsers = @[
            @{@"name": @"Charlie", @"email": @"[email protected]", @"age": @(35)},
            @{@"name": @"Diana", @"email": @"[email protected]", @"age": @(28)},
            @{@"name": @"Eve", @"email": @"[email protected]", @"age": @(22)}
        ];

        [batchInserter batchInsertUsers:newUsers];

        NSArray *newProducts = @[
            @{@"name": @"Laptop", @"price": @(999.99), @"quantity": @(10), @"category": @"Electronics"},
            @{@"name": @"Mouse", @"price": @(25.50), @"quantity": @(50), @"category": @"Electronics"}
        ];

        [batchInserter batchInsertProducts:newProducts];

        // 3. Savepoints
        NSLog(@"\n--- Savepoints ---");

        SavepointManager *spManager = [[SavepointManager alloc] initWithDatabase:database];

        [spManager createSavepoint:@"before_changes"];

        sqlite3_exec(database, "INSERT INTO users (name, email, age) VALUES ('Frank', '[email protected]', 40);", NULL, NULL, NULL);
        sqlite3_exec(database, "INSERT INTO users (name, email, age) VALUES ('Grace', '[email protected]', 32);", NULL, NULL, NULL);

        NSLog(@"Inserted users");

        [spManager rollbackToSavepoint:@"before_changes"];
        NSLog(@"Rolled back to before_changes");

        [spManager releaseSavepoint:@"before_changes"];

        // 4. Atomic updates
        NSLog(@"\n--- Atomic Updates ---");

        AtomicUpdater *updater = [[AtomicUpdater alloc] initWithDatabase:database];

        [updater transferAmount:200.00
                fromAccount:1
                  toAccount:2];

        NSLog(@"Transferred amount");

        // Check balances
        sqlite3_stmt *statement;
        if (sqlite3_prepare_v2(database, "SELECT id, balance FROM accounts;", -1, &statement, NULL) == SQLITE_OK) {
            NSLog(@"Account balances:");

            while (sqlite3_step(statement) == SQLITE_ROW) {
                NSInteger accountId = sqlite3_column_int(statement, 0);
                double balance = sqlite3_column_double(statement, 1);
                NSLog(@"  Account %ld: $%.2f", (long)accountId, balance);
            }

            sqlite3_finalize(statement);
        }

        // Cleanup
        sqlite3_close(database);

        NSLog(@"\n=== SQLite Transactions Examples Completed ===");
    }

    return 0;
}