Is cursor a free win?

by Prashanth Kumar Duggishetti
AI Programming Cursor

I love using AI - who doesn't? It improves productivity so much, right? :) As a part-time programmer and full-time venture studio manager, I'm constantly exploring productivity improvement strategies for my team and the companies I work with. I'm not particularly good, but I try to understand the underlying reasons for things and extrapolate from there.

For someone like me - a self-taught wannabe programmer - tools like Cursor look like a boon or are they? I can pick up something and kickstart projects without needing much learning right, thats the promise right!. So I gave Cursor a try a few times. But every time I used it, it seemed to miss something and took me down rabbit holes I would never have gone down otherwise. Why? Well, every time I built something using Cursor or pure AI, I spent hours at the end of the day fixing bugs that the AI introduced within the first 30 minutes. I learned quite a bit debugging well-written but crappy code.

The other day, I thought maybe I was using the tool wrong, so I went on Cursor's website and started reading. Something interesting caught my eye - as you scroll down, there's a heading that says "Edit in natural language," and under that, a video showing a Python function for storing failed transactions from Stripe into a DB. The video demonstrates parallelizing this function using AI.

The Original Code

def insert_failed_transactions_from_stripe():
    import sqlite3
    import stripe
    from datetime import datetime, timedelta

    # Set up Stripe API key
    stripe.api_key = 'your_stripe_api_key_here'

    # Connect to SQLite database
    conn = sqlite3.connect('transactions.db')
    cursor = conn.cursor()

    # Create table if it doesn't exist
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS transactions (
        id TEXT PRIMARY KEY,
        amount INTEGER,
        currency TEXT,
        status TEXT,
        created TIMESTAMP
    )
    ''')

    # Get today's date range
    start_of_day = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    end_of_day = start_of_day + timedelta(days=1)

    # Fetch transactions from Stripe
    transactions = stripe.Charge.list(
        created={
            'gte': int(start_of_day.timestamp()),
            'lt': int(end_of_day.timestamp())
        }
    )

    for transaction in transactions.auto_paging_iter():
        if transaction.status == "failed":
            cursor.execute('''
            INSERT OR IGNORE INTO transactions (id, amount, currency, status, created)
            VALUES (?, ?, ?, ?, ?)
            ''', (transaction.id, transaction.amount, transaction.currency, transaction.status, datetime.fromtimestamp(transaction.created)))

    conn.commit()
    conn.close()

The prompt given to AI was simply: "Parallelize this because the API is slow"

The AI's Solution

def insert_failed_transactions_from_stripe():
    import sqlite3
    import stripe
    from datetime import datetime, timedelta
    from concurrent.futures import ThreadPoolExecutor

    # Set up Stripe API key
    stripe.api_key = 'your_stripe_api_key_here'

    # Connect to SQLite database
    conn = sqlite3.connect('transactions.db')
    cursor = conn.cursor()

    # Create table if it doesn't exist
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS transactions (
        id TEXT PRIMARY KEY,
        amount INTEGER,
        currency TEXT,
        status TEXT,
        created TIMESTAMP
    )
    ''')

    # Get today's date range
    start_of_day = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    end_of_day = start_of_day + timedelta(days=1)

    # Fetch transactions from Stripe
    transactions = stripe.Charge.list(
        created={
            'gte': int(start_of_day.timestamp()),
            'lt': int(end_of_day.timestamp())
        }
    )

    def insert_transaction(transaction):
        if transaction.status == "failed":
            cursor.execute('''
            INSERT OR IGNORE INTO transactions (id, amount, currency, status, created)
            VALUES (?, ?, ?, ?, ?)
            ''', (transaction.id, transaction.amount, transaction.currency, transaction.status, datetime.fromtimestamp(transaction.created)))

    with ThreadPoolExecutor() as executor:
        executor.map(insert_transaction, transactions.auto_paging_iter())

    conn.commit()
    conn.close()

This had me thinking - what's wrong with this implementation? The most glaring issue is that the same database connection and cursor(not the tool) are being shared across multiple threads. This is a recipe for disaster with SQLite, which isn't designed for this kind of concurrent access pattern. The connection object isn't thread-safe, and this will likely lead to "database is locked" errors or even data corruption.

Identifying the Real Bottleneck First

Before diving into any optimization, I would first time the function to identify where the actual bottleneck is. The real change needed isn't blindly following AI suggestions but developing the thought process to identify bottlenecks properly.

Here's how I'd modify the original code to time each part, again ai is awesome at Something like this laborious task.

def insert_failed_transactions_from_stripe():
    import sqlite3
    import stripe
    from datetime import datetime, timedelta
    import time

    # Set up Stripe API key
    stripe.api_key = 'your_stripe_api_key_here'

    # Connect to SQLite database
    start_time = time.time()
    conn = sqlite3.connect('transactions.db')
    cursor = conn.cursor()

    # Create table if it doesn't exist
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS transactions (
        id TEXT PRIMARY KEY,
        amount INTEGER,
        currency TEXT,
        status TEXT,
        created TIMESTAMP
    )
    ''')
    db_setup_time = time.time() - start_time
    print(f"Database setup time: {db_setup_time:.4f} seconds")

    # Get today's date range
    start_of_day = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    end_of_day = start_of_day + timedelta(days=1)

    # Fetch transactions from Stripe
    api_start_time = time.time()
    transactions = stripe.Charge.list(
        created={
            'gte': int(start_of_day.timestamp()),
            'lt': int(end_of_day.timestamp())
        }
    )
    api_fetch_time = time.time() - api_start_time
    print(f"API fetch time: {api_fetch_time:.4f} seconds")

    # Process and insert transactions
    insert_start_time = time.time()
    transaction_count = 0
    for transaction in transactions.auto_paging_iter():
        if transaction.status == "failed":
            cursor.execute('''
            INSERT OR IGNORE INTO transactions (id, amount, currency, status, created)
            VALUES (?, ?, ?, ?, ?)
            ''', (transaction.id, transaction.amount, transaction.currency, transaction.status, datetime.fromtimestamp(transaction.created)))
            transaction_count += 1

    insert_time = time.time() - insert_start_time
    print(f"Database insert time: {insert_time:.4f} seconds for {transaction_count} transactions")

    commit_start_time = time.time()
    conn.commit()
    commit_time = time.time() - commit_start_time
    print(f"Database commit time: {commit_time:.4f} seconds")

    conn.close()
    total_time = time.time() - start_time
    print(f"Total execution time: {total_time:.4f} seconds")

There's a possibility that the API calls could be the bottleneck, but there's also a chance it's the database operations. Without timing, we're just guessing.

The Core Assumptions the AI Makes

  1. The DB is in WAL mode, so it can be parallelized
  2. The database operations are the bottleneck, not the API calls
  3. A single database connection can be safely shared across threads

If it were really smart, the first thing AI would do is implement either of the 2 solutions down here, without prompting

Better Solutions

Solution 1: Check WAL Mode First, Then Thread Properly

def insert_failed_transactions_from_stripe():
    import sqlite3
    import stripe
    from datetime import datetime, timedelta
    from concurrent.futures import ThreadPoolExecutor

    # Set up Stripe API key
    stripe.api_key = 'your_stripe_api_key_here'

    # Connect to SQLite database and enable WAL mode
    conn = sqlite3.connect('transactions.db')
    conn.execute('PRAGMA journal_mode=WAL')

    # Create table if it doesn't exist
    conn.execute('''
    CREATE TABLE IF NOT EXISTS transactions (
        id TEXT PRIMARY KEY,
        amount INTEGER,
        currency TEXT,
        status TEXT,
        created TIMESTAMP
    )
    ''')

    # Get today's date range
    start_of_day = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    end_of_day = start_of_day + timedelta(days=1)

    # Fetch transactions from Stripe
    transactions = stripe.Charge.list(
        created={
            'gte': int(start_of_day.timestamp()),
            'lt': int(end_of_day.timestamp())
        }
    )

    def insert_transaction(transaction):
        if transaction.status == "failed":
            # Create a new connection for each thread
            local_conn = sqlite3.connect('transactions.db')
            local_conn.execute('''
            INSERT OR IGNORE INTO transactions (id, amount, currency, status, created)
            VALUES (?, ?, ?, ?, ?)
            ''', (transaction.id, transaction.amount, transaction.currency,
                  transaction.status, datetime.fromtimestamp(transaction.created)))
            local_conn.commit()
            local_conn.close()

    with ThreadPoolExecutor() as executor:
        executor.map(insert_transaction, transactions.auto_paging_iter())

    conn.close()

Solution 2: Real Async with aiosqlite

async def insert_failed_transactions_from_stripe():
    import aiosqlite
    import stripe
    import asyncio
    from datetime import datetime, timedelta

    # Set up Stripe API key
    stripe.api_key = 'your_stripe_api_key_here'

    # Get today's date range
    start_of_day = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
    end_of_day = start_of_day + timedelta(days=1)

    # Fetch transactions from Stripe
    transactions = stripe.Charge.list(
        created={
            'gte': int(start_of_day.timestamp()),
            'lt': int(end_of_day.timestamp())
        }
    )

    # Connect to SQLite database
    async with aiosqlite.connect('transactions.db') as db:
        # Enable WAL mode
        await db.execute('PRAGMA journal_mode=WAL')

        # Create table if it doesn't exist
        await db.execute('''
        CREATE TABLE IF NOT EXISTS transactions (
            id TEXT PRIMARY KEY,
            amount INTEGER,
            currency TEXT,
            status TEXT,
            created TIMESTAMP
        )
        ''')

        # Prepare insert tasks
        insert_tasks = []
        for transaction in transactions.auto_paging_iter():
            if transaction.status == "failed":
                task = db.execute('''
                INSERT OR IGNORE INTO transactions (id, amount, currency, status, created)
                VALUES (?, ?, ?, ?, ?)
                ''', (transaction.id, transaction.amount, transaction.currency,
                      transaction.status, datetime.fromtimestamp(transaction.created)))
                insert_tasks.append(task)

        # Execute all inserts concurrently
        if insert_tasks:
            await asyncio.gather(*insert_tasks)
            await db.commit()

If you observe keenly, all I did here was theorize with AI, which became easier yet simultaneously concerning when considering the original problem at hand. In reality, even I'm uncertain whether everything I theorized is correct.

Overall, I couldn't reach a conclusion about whether AI makes my team more productive. I haven't given up on AI - I'm establishing workflows for myself to determine where AI provides value in programming workflows.

Update: It appears Cursor has now removed this example from their site and is pushing more aggressively toward the agentic coding approach.