Menu

Calling SQLite3 from GnuCOBOL (without a C wrapper)

2015-11-15
2016-10-11
  • Robert W.Mills

    Robert W.Mills - 2015-11-15

    I'm trying to use SQLite3 natively with GnuCOBOL.

    The reason for not using a C wrapper is:
    1. I don't write C.
    2. All the examples I've seen only send their SELECT output to stdout, which I find very unuseful, instead of allowing you to read it one row at a time into your program.
    3. I still don't write C <G>.

    My plan is to create a library of GnuCOBOL callable routines. More than likely they will have a macro wrapper to make them easier to use.

    It looks as if my open routine works OK. It creates a new file that the SQLite3 command is happy with (the sqlite_master table exists and you can access it; you can create tables and insert/select/update rows).

    All of my other routines, even close, return a status 21 which is "Attempting to use an SQLite interface in an undefined/unsupported way.".

    I think that the error might be caused by the way I have defined (s9(4) comp) the variable to hold the 'handle' to the database which is returned by the open and used by most of the other routines.

    I have attached a copy of my test program source and request that somebody who is conversant with calling C routines from GnuCOBOL have a look at it and give me a hint on what my mistake is.

    Note: I am developing this on a 32-bit system.

     

    Last edit: Robert W.Mills 2015-11-16
  • PatMcC

    PatMcC - 2015-11-16

    Hi Robert

    I don't know the sqlite C API well at all and I won't be able to see errors with this but on a 64 bit system I have had good luck with usage is binary-long, maybe give that a try ...

    -Pat

     
  • PatMcC

    PatMcC - 2015-11-16

    one other thing... I have patterned most of my calls in to C after Brian's work. Even if your not into GTK you might find some good tips looking through his work.

    I think I have more of his current code on my machine but here is a link I could find at the moment:
    https://sourceforge.net/p/open-cobol/contrib/HEAD/tree/trunk/tools/cobweb/cobweb-gtk/

     
  • Brian Tiffin

    Brian Tiffin - 2015-11-16

    Looks nice Robert, and please continue. :-)

    I threatened to try and code up user functions for SQLite3 a year or so ago, but that todo hasn't bubbled its way to the top of the pile yet.

    One of the main things when interfacing to libraries is the issue of C's view of the handle versus the COBOL view of the handle.

    Your sqlite3-open works fine, as the C library expects and address of where to store a handle.

    BY REFERENCE db-handle
    

    Most calls after that, expect the contents of that db-handle, not the address of the COBOL field. Use

    BY VALUE db-handle
    

    in almost all later calls (including sqlite3-close) and you'll be fine. C wants the "C view" of the handle, not the COBOL by reference address.

    I haven't changed all the code yet, but tried quickly with 2 things.

    Changed db-handle to POINTER and (or here on a 64 bit machine s9(18) comp would accomplish the proper sizing. I find pointer easier, as it just does the right thing.

      call "sqlite3_prepare_v2" using by value db-handle,
                                      by content concatenate(trim(sql-statement), x"00")
                                      by value num-bytes,
                                      by reference pStmt,
                                      zTail
                            returning db-status
    

    Gives

    $ COB_SET_TRACE=y ./testsqlite3 
    Source:     'testsqlite3.cob'
    Program-Id: testsqlite3      Entry:     testsqlite3            Line: 108
    Program-Id: testsqlite3      Section:   (None)                 Line: 108
    Program-Id: testsqlite3      Paragraph: testsqlite3-mainline   Line: 108
    Program-Id: testsqlite3      Statement: MOVE                   Line: 110
    Program-Id: testsqlite3      Statement: PERFORM                Line: 111
    Program-Id: testsqlite3      Paragraph: s001-open-database     Line: 139
    Program-Id: testsqlite3      Statement: CALL                   Line: 144
    Program-Id: testsqlite3      Statement: EVALUATE               Line: 149
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 152
    DEBUG: database opened
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 153
    DEBUG: database handle [0x0000000001a8d048]
    Program-Id: testsqlite3      Statement: CONTINUE               Line: 154
    Program-Id: testsqlite3      Statement: MOVE                   Line: 113
    Program-Id: testsqlite3      Statement: PERFORM                Line: 114
    Program-Id: testsqlite3      Paragraph: s003-sql-compile       Line: 193
    Program-Id: testsqlite3      Statement: MOVE                   Line: 198
    Program-Id: testsqlite3      Statement: ADD                    Line: 199
    Program-Id: testsqlite3      Statement: MOVE                   Line: 201
    Program-Id: testsqlite3      Statement: CALL                   Line: 203
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 210
    0x0000000000000000, -8079, +0001
    Program-Id: testsqlite3      Statement: EVALUATE               Line: 212
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 219
    DEBUG: sql statement compile failed
    Program-Id: testsqlite3      Statement: PERFORM                Line: 220
    Program-Id: testsqlite3      Paragraph: s000-sqlite-error      Line: 124
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 134
    SQLite3 ERROR: +0001
    Program-Id: testsqlite3      Statement: GOBACK                 Line: 223
    Program-Id: testsqlite3      Exit:      testsqlite3
    

    Thats ok, as there is no foo to select on. After running

    prompt$ sqlite3 test.sdb
    sqlite> create table foo(x integer, y, z);
    sqlite> .q
    

    testsqlite3 now runs as

    $ COB_SET_TRACE=y ./testsqlite3 
    Source:     'testsqlite3.cob'
    Program-Id: testsqlite3      Entry:     testsqlite3            Line: 108
    Program-Id: testsqlite3      Section:   (None)                 Line: 108
    Program-Id: testsqlite3      Paragraph: testsqlite3-mainline   Line: 108
    Program-Id: testsqlite3      Statement: MOVE                   Line: 110
    Program-Id: testsqlite3      Statement: PERFORM                Line: 111
    Program-Id: testsqlite3      Paragraph: s001-open-database     Line: 139
    Program-Id: testsqlite3      Statement: CALL                   Line: 144
    Program-Id: testsqlite3      Statement: EVALUATE               Line: 149
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 152
    DEBUG: database opened
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 153
    DEBUG: database handle [0x0000000002469048]
    Program-Id: testsqlite3      Statement: CONTINUE               Line: 154
    Program-Id: testsqlite3      Statement: MOVE                   Line: 113
    Program-Id: testsqlite3      Statement: PERFORM                Line: 114
    Program-Id: testsqlite3      Paragraph: s003-sql-compile       Line: 193
    Program-Id: testsqlite3      Statement: MOVE                   Line: 198
    Program-Id: testsqlite3      Statement: ADD                    Line: 199
    Program-Id: testsqlite3      Statement: MOVE                   Line: 201
    Program-Id: testsqlite3      Statement: CALL                   Line: 203
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 210
    0x000000000247e5c8, -8143, +0000
    Program-Id: testsqlite3      Statement: EVALUATE               Line: 212
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 215
    DEBUG: sql statement compiled
    Program-Id: testsqlite3      Statement: CONTINUE               Line: 216
    Program-Id: testsqlite3      Statement: PERFORM                Line: 116
    Program-Id: testsqlite3      Paragraph: s002-close-database    Line: 164
    Program-Id: testsqlite3      Statement: CALL                   Line: 169
    Program-Id: testsqlite3      Statement: EVALUATE               Line: 173
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 184
    DEBUG: database close error
    Program-Id: testsqlite3      Statement: PERFORM                Line: 185
    Program-Id: testsqlite3      Paragraph: s000-sqlite-error      Line: 124
    Program-Id: testsqlite3      Statement: DISPLAY                Line: 134
    SQLite3 ERROR: +0021
    Program-Id: testsqlite3      Statement: GOBACK                 Line: 188
    Program-Id: testsqlite3      Exit:      testsqlite3
    

    (I didn't modify any other code, so the handle management is still wrong for the close call etc...)

    You are on the right track, Robert. Make changes like the one listed above for prepare_v2 and you'll be wielding sqlite3 like its a nodachi ready to take on the hordes.

    Cheers,
    Brian

     
  • Robert W.Mills

    Robert W.Mills - 2015-11-16

    Brian,

    Thanks for the encouragement and the info.

    A few minutes making your suggested changes and the addition of some more code has left me with a program that can do the following:

    1. Open an existing or new database.
    2. Create a table.
    3. Insert data into the table.
    4. Select data from the table (the routine reports that data has been returned although it has not done anything with it, its sitting in a 'virtual' array waiting to be retrieved).
    5. Close the database (automatically saving it if it is new).

    The routine code used to do items 2 & 3 is the same. The only difference is in the SQL statement that you pass to it. This means that deleting and updating data only requires a different SQL statement.

    Now to do the hard part. Extract the data from the 'virtual' array and return it to the program one row at a time.

     
    • Brian Tiffin

      Brian Tiffin - 2015-11-16

      Nice to hear, Robert. It gets flowing usually, and then the brain scratchers surface. :-)

      For that hard part. USAGE POINTER BASED is your friend. Take a pointer from a sqlite3 function, set the address of your based pointer to it, and you can fairly effectively dereference C addresses and then wield the data from COBOL, by setting the address of a

      01 sql-row pic x(1024) based.
      

      field. Then string the data delimited by null into an actual working store field. Then unstring that into the row's columns. An issue with SQLite; It is a willy nilly typed database. Just because you create a field as integer, does not mean you can only stash integers in it. Each row can have different types and different lengths for each and every column. The data typing and validation is squarely on the shoulders of the application writer, the engine will not complain when variant data is stored in a field (except for a single primary key integer rowid field, all fields can hold any data, and I think recent releases even got rid of that rule). Having said that, if you always write 20 characters to the second field, you will always get 20 characters back. (Until someone opens your table and inserts something different in the column). What you see in .schema is just a (very weak) suggestion and has no teeth in SQLite tables.

      And one point on based pointers. (And I've learned the hard way a few times). If you set a pointer address and "do things" with it, like SET addr UP BY or DOWN BY you may be modifying data you don't own, and that can lead to some real head scratching. So, it's usually worthwhile to include a third field, not based, that you set from the based pointer and then UP and DOWN with that instead. You'll be safely modifying working-storage and not some external library's internal pointer.

      As a for instance. I was mucking around trying to display the process environment space, environ is a pointer to an array of pointers, so I used the BASED pointer idiom. Then happily traversed the array, and displayed the strings. Then smiled and posted code.

      Turns out the code was modifying the process global environ pointer. And other programs in the process space went kablooey. Mysteriously and quite loudly, kablooey. Lesson learned, then forgotten, then learned again, and forgotten a second time. I think it has stuck now.

      USAGE POINTER BASED is to be treated as a readonly field. :-)

      Cheers

       

      Last edit: Brian Tiffin 2015-11-16
      • Robert W.Mills

        Robert W.Mills - 2015-11-16

        One of those 'brain scratchers' has just surfaced (speak of the devil and he WILL appear).

        I've spent about 2 hours on trying to work out how I get the value from a text column. Have worked out part of it but the last part is being a bit of a b*#$h.

        The C code says

        const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
        

        I translated this to

        call "sqlite3_column_text" using by reference stmt, by value iCol
                returning tCol
        end-call
        

        where stmt is usage pointer, iCol is s9(4) comp & tCol is x(1024). iCol is the number of bytes in the column as returned by sqlite3_column_bytes.

        The conclusion I have come to is that sqlite3_column_text is actually returning an address and I have to move the data starting at that address, for iCol bytes, to tCol. Am I getting close?

         

        Last edit: Robert W.Mills 2015-11-16
  • Brian Tiffin

    Brian Tiffin - 2015-11-16

    stmt, you need to pass BY VALUE. It was by reference during prepare but now the engine wants the content of your pointer, not the address of your pointer.

    And it returns a pointer, which you'll need to dereference, ala

        01 pCol usage pointer.
        01 sqlite-data   pic x(1024) based.
        01 safer-data    pic x(1024).
    
        call "sqlite3_column_text" using by value stmt by value iCol
            returning pCol
    
        set address of sqlite-data to pCol
        string sqlite-data delimited by low-value into safer-data
    

    sqlite-data will be very transitory, so get it out and into working-storage as soon as you can. By that I mean, don't leave it lying around while you do other work, best to snag it out immediately after the call. Once its in working-store at safer-data you can trust it not to shuffle out under foot because somebody sneezed. Oh, and don't modify sqlite-data, it's memory owned by the library and changing it is a no-no. Treat it as read only. Well fenced code might actually

    set address of sqlite-data to NULL
    

    as soon as the STRING operation was complete, to protect the SQLite library space from unintentional change.

    For debugging, this can come in handy.

        call "printf" using by content "sq3: %s" & x"0a00"  by value pCol
    

    that actually returns a count of characters printed, but just about everybody ignores that (although RETURN-CODE will have the number if you need it or are curious).

    And explaining the printf "%s" displays a null terminated string. the x"0a00" passes in a newline and null which in C usually looks like "%s\n". COBOL needs to hardcode the 10, and the terminating 0 for the printf format spec. printf is a "vararg" kind of function. The spec determines how many values you need to put in the call frame. In the %s case, just the one address will do. But you could

    call "printf" using by content "sq3: %s and %d" &x"0a00" 
        by value pCol
        by value iCol
    

    etc... the % marker in the spec determines how many other fields to pass (in general, there are edge case, but by and large, the number of % symbols determines how many fields to pass).

    And don't pass iCol to a %s, as C will then try to dereference your integer and try to access memory at (for instance) address 1, which is a kablooey address.

    Cheers,
    Brian

     

    Last edit: Brian Tiffin 2015-11-16
    • Robert W.Mills

      Robert W.Mills - 2015-11-16

      10 minutes after my last post on this I decided to add the code to get the column names. The second line of the sqlite3_column_name page reads

      "The sqlite3_column_name() interface returns a pointer to a zero-terminated UTF-8 string..."

      The moment I saw this I understood. If they had inculded this sentence in the page for sqlite3_column_text it would have saved a lot of head scratching.

      To top it all I lost my broadband connection, just got it back :), and couldn't let you know I'd found the answer.

       
  • Brian Tiffin

    Brian Tiffin - 2015-11-17

    Yeah, some API docs require other API docs which lead back to docs about stuff you are already supposed to know, but couldn't know because the first page led to the ...

    :-)

    Richard Hipp is a genius, and high level usage materials are usually top-notch, and world-class easy to understand, but internals can take a little digging. He may view the world as being two types, grandma and guru. But he seems to forget to leave the ladder grandma needs to climb to get to see the guru. Fossil is the same way. Nice and easy to use, but to make a change or look under the hood means taking apart the whole car.

    And no worries on not being able to respond earlier, which let me make that intermediate post. It might help someone else in the not too distant future.

    Cheers

     
  • Robert W.Mills

    Robert W.Mills - 2015-11-17

    Find attached the latest version of my test program before I turn it into a library of some sorts. Feel free to give it a try and make changes. Please let me know if you find any problems. There is loads of comments (about 80..90 lines worth at the end) ala Brian style.

     
  • Brian Tiffin

    Brian Tiffin - 2015-11-18

    More cool, Robert.

    Worked the charm here.

    prompt$ ./SQL3I4GC-test                    
     line_no   line_text                                                    
     -------   ------------------------------------------------------------ 
        0001   this is line 1                                               
        0002   this is line 2                                               
        0003   this is line 3                                               
    
    -- End of Report --
    

    Nice. The GnuCOBOL world gets a little bit bigger.

    I've attached the output from

    make SQL3I4GC.html
    

    after two small edits to let rst deal with some indents/dedents (in the description of IS_DB_OPEN) and adding a !rst-marker! onto the >>ELSE line.

    Inline docs are awesome.

    By the by, you don't really need to use the underscores for the COBOL names, go dash, easier on the eyes and the fingers. And don't worry about being able to get at the names from libraries, GnuCOBOL does reasonable mangling, and COBOL folk don't even have to worry, as the naming during call is all handled as well.

    I already had a foo table in test.sdb, from earlier testing. You should probably just prepare a "drop table foo;" statement to avoid

    *E* Compile of SQL statement failed.
    table foo already exists
    SQLite3 Status Code: +0001
    

    for your demo, seeing as your just going to create it again anyway for the next run. Or, to accumulate records, go with

    create table foo (line_no int, line_text text) if not exists;
    

    One last one, though this could be make work. Dave Nicolette has written up a marvelous cobol-unit-test framework that lets you setup unit tests of individual paragraphs.

    All in a nice test domain specific language that reads like COBOL. A few minutes to set up (ok, maybe an hour to get to grips with it), a few minutes to write the test cases, and you'll have automated regression testing at whim from that point forward. Worth a look I think, Robert. http://opencobol.add1tocobol.com/gnucobol/#what-is-cobol-unit-test

    Edit: oops, old link, habit...

    http://open-cobol.sourceforge.net/faq/#what-is-cobol-unit-test will be the new home, as of a few minutes ago.

    Cheers

     

    Last edit: Brian Tiffin 2015-11-18
  • Stefan Bodingh

    Stefan Bodingh - 2015-11-18

    Looks interesting, but what changes do I have to implement to run if my GnuCOBOL is compiled with MS Visual Studio 2012?

     
    • Robert W.Mills

      Robert W.Mills - 2015-11-18

      Stefan,

      My answer to your question is "I don't know. I no longer have access to a Windows box (moved from WinXP to Linux Mint last year) and have never used VS.".

      I hope that somebody who uses GnuCOBOL and VS sees your question and responds.
      Anybody?

       
    • Simon Sobisch

      Simon Sobisch - 2015-11-19

      As the GNU/Linux tectonics are:

      prompt$ # Install the SQLite3 library (sqlite.org), if required.
      prompt$ cobc -x -fdebugging-line testsqlite3.cob -lsqlite3
      prompt$ ./testsqlite3
      prompt$ rm foo.sdb    # to delete test database when finished.
      

      The Windows tectonics are

      prompt$ :: Install the SQLite3 library (sqlite.org), if required.
      prompt$ cobc -x -fdebugging-line testsqlite3.cob -lsqlite3
      prompt$ .\testsqlite3
      prompt$ erase foo.sdb    :: to delete test database when finished.
      

      In both cases it is likely a good idea to add -L /path/to/sqlite/lib to cobc command line.

       
  • Robert W.Mills

    Robert W.Mills - 2015-11-19

    Just realised that a function to test if the database is open/closed is not required. Just add the following 88 level to the db-handle variable:

    88  database-closed          value NULL.
    

    then all you have to do is:

    IF database-closed THEN
        *> action(s) to perfor if database is closed.
    ELSE
        *> action(s) to perform if database is open.
    END-IF
    

    or

    IF NOT database-closed THEN
        *> action(s) to perfor if database is open.
    ELSE
        *> action(s) to perform if database is closed.
    END-IF
    

    KISS is your friend.

     

    Last edit: Robert W.Mills 2015-11-19
  • Simon Sobisch

    Simon Sobisch - 2015-11-19

    Just a hint, especially for Robert: I'd highly suggest to use CALL STATIC 'cfunc' as this resolves all dependencies at compile time (needs a 2.x cobc one).

     
    • Robert W.Mills

      Robert W.Mills - 2015-11-19

      Simon, Am getting the following messages when I add STATIC after the CALL

      /tmp/cob11120_0.c:2225:5: warning: implicit declaration of function sqlite3_errmsg [-Wimplicit-function-declaration]
         (*(unsigned char **) (b_51)) = (void *)sqlite3_errmsg ((*(unsigned char **) (b_47)));
         ^
      /tmp/cob11120_0.c:2265:3: warning: implicit declaration of function sqlite3_open [-Wimplicit-function-declaration]
        b_1 = sqlite3_open (cob_procedure_params[0]->data, b_47);
        ^
      /tmp/cob11120_0.c:2293:3: warning: implicit declaration of function sqlite3_close [-Wimplicit-function-declaration]
        b_1 = sqlite3_close ((*(unsigned char **) (b_47)));
        ^
      /tmp/cob11120_0.c:2453:3: warning: implicit declaration of function sqlite3_prepare_v2 [-Wimplicit-function-declaration]
        b_1 = sqlite3_prepare_v2 ((*(unsigned char **) (b_47)), cob_procedure_params[1]->data, (cob_s32_t)(((short)COB_BSWAP_16(*(short *)(b_59)))), b_49, NULL);
        ^
      /tmp/cob11120_0.c:2474:3: warning: implicit declaration of function sqlite3_step [-Wimplicit-function-declaration]
        b_1 = sqlite3_step ((*(unsigned char **) (b_49)));
        ^
      /tmp/cob11120_0.c:2527:3: warning: implicit declaration of function sqlite3_finalize [-Wimplicit-function-declaration]
        b_1 = sqlite3_finalize ((*(unsigned char **) (b_49)));
        ^
      /tmp/cob11120_0.c:2539:3: warning: implicit declaration of function sqlite3_reset [-Wimplicit-function-declaration]
        b_1 = sqlite3_reset ((*(unsigned char **) (b_49)));
        ^
      /tmp/cob11120_0.c:2567:5: warning: implicit declaration of function sqlite3_column_int [-Wimplicit-function-declaration]
         b_1 = sqlite3_column_int ((*(unsigned char **) (b_49)), (cob_s32_t)(((short)COB_BSWAP_16(*(short *)(b_36)))));
         ^
      /tmp/cob11120_0.c:2581:5: warning: implicit declaration of function sqlite3_column_bytes [-Wimplicit-function-declaration]
         b_1 = sqlite3_column_bytes ((*(unsigned char **) (b_49)), (cob_s32_t)(((short)COB_BSWAP_16(*(short *)(b_36)))));
         ^
      /tmp/cob11120_0.c:2588:5: warning: implicit declaration of function sqlite3_column_text [-Wimplicit-function-declaration]
         (*(unsigned char **) (b_62)) = (void *)sqlite3_column_text ((*(unsigned char **) (b_49)), (cob_s32_t)(((short)COB_BSWAP_16(*(short *)(b_36)))));
         ^
      /tmp/cob11120_0.c:2654:3: warning: implicit declaration of function sqlite3_column_type [-Wimplicit-function-declaration]
        b_1 = sqlite3_column_type ((*(unsigned char **) (b_49)), (cob_s32_t)(((short)COB_BSWAP_16(*(short *)(b_36)))));
        ^
      /tmp/cob11120_0.c:2667:3: warning: implicit declaration of function sqlite3_column_name [-Wimplicit-function-declaration]
        (*(unsigned char **) (b_61)) = (void *)sqlite3_column_name ((*(unsigned char **) (b_49)), (cob_s32_t)(((short)COB_BSWAP_16(*(short *)(b_36)))));
        ^
      /tmp/cob11120_0.c:2686:3: warning: implicit declaration of function sqlite3_column_count [-Wimplicit-function-declaration]
        b_1 = sqlite3_column_count ((*(unsigned char **) (b_49)));
        ^
      

      If I remove the STATIC everything is OK.

       

      Last edit: Robert W.Mills 2015-11-19
      • Simon Sobisch

        Simon Sobisch - 2015-11-19

        I thought we have fixed that, maybe its one of the missing merges from reportwriter-branch, I'll check and report in the next days.

         
        • Robert W.Mills

          Robert W.Mills - 2015-11-19

          Simon, hold off on that check. I'm still on r411, downloaded r658 but not had a chance to update to it yet.

           
          • Simon Sobisch

            Simon Sobisch - 2016-10-11

            Time is ticking away... this was just merged recently (after rc1)... another thing that I want to add is "include C headers", I may check this week if this works fine.

             
  • PatMcC

    PatMcC - 2015-11-19

    Thanks for mentioning this Simon, this will be really valuable.

    -Pat

     
  • Simon Sobisch

    Simon Sobisch - 2015-11-20

    Just FYI: The generation of function declarations for removing C compiler warnings was done by Ron in [r581] and [r582]. It's currently not merged into 2.0 (will be, together with the missing test cases).

     
    • Vincent (Bryan) Coen

      Any chance of the time frame for this along with the RW addons?

      Should point out that on commits for 664 I had failures during NIST
      testing on both gnu and rw branches.


      Mod edit: remove some reply-to

       

      Last edit: Brian Tiffin 2015-11-20
  • Brian Tiffin

    Brian Tiffin - 2016-02-27

    Looking more and more forward to SQLite in GnuCOBOL. Richard Hipp is a genuis. Recent addition of Common Table Expressions (CTE) makes for some wonky fun SQL.

    WITH RECURSIVE
      xaxis(x) AS (VALUES(-2.0) UNION ALL SELECT x+0.05 FROM xaxis WHERE x<1.2),
      yaxis(y) AS (VALUES(-1.0) UNION ALL SELECT y+0.1 FROM yaxis WHERE y<1.0),
      m(iter, cx, cy, x, y) AS (
        SELECT 0, x, y, 0.0, 0.0 FROM xaxis, yaxis
        UNION ALL
        SELECT iter+1, cx, cy, x*x-y*y + cx, 2.0*x*y + cy FROM m
         WHERE (x*x + y*y) < 4.0 AND iter<28
      ),
      m2(iter, cx, cy) AS (
        SELECT max(iter), cx, cy FROM m GROUP BY cx, cy
      ),
      a(t) AS (
        SELECT group_concat( substr(' .+*#', 1+min(iter/7,4), 1), '')
        FROM m2 GROUP BY cy
      )
    SELECT group_concat(rtrim(t),x'0a') FROM a;
    

    and

    prompt$ sqlite3
    sqlite> .read mandle.sql
                                        ....#
                                       ..#*..
                                     ..+####+.
                                .......+####....   +
                               ..##+*##########+.++++
                              .+.##################+.
                  .............+###################+.+
                  ..++..#.....*#####################+.
                 ...+#######++#######################.
              ....+*################################.
     #############################################...
              ....+*################################.
                 ...+#######++#######################.
                  ..++..#.....*#####################+.
                  .............+###################+.+
                              .+.##################+.
                               ..##+*##########+.++++
                                .......+####....   +
                                     ..+####+.
                                       ..#*..
                                        ....#
                                        +.
    

    Fractal SQL.

    Cheers,
    Brian

     
    👍
    1

Anonymous
Anonymous

Add attachments
Cancel





Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.