Path: csiph.com!usenet.pasdenom.info!weretis.net!feeder1.news.weretis.net!feeder.erje.net!eu.feeder.erje.net!newsfeed.xs4all.nl!newsfeed2.news.xs4all.nl!xs4all!newsgate.cistron.nl!newsgate.news.xs4all.nl!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'python.': 0.02; 'python,': 0.02; 'insert': 0.05; 'subject:Python': 0.06; 'debugging': 0.07; 'error:': 0.07; 'postgresql': 0.07; 'python3': 0.07; 'referring': 0.07; 'tool,': 0.07; 'alter': 0.09; 'app,': 0.09; 'back-end': 0.09; 'broke': 0.09; 'default.': 0.09; 'directions': 0.09; 'keys,': 0.09; 'logic': 0.09; 'option,': 0.09; 'postgresql,': 0.09; 'statements': 0.09; 'structure,': 0.09; 'subject:2.7': 0.09; 'terminated': 0.09; 'val': 0.09; 'violates': 0.09; 'api': 0.11; 'cc:addr:python-list': 0.11; 'python': 0.11; 'django': 0.11; 'gui': 0.12; '(meaning': 0.16; '23,': 0.16; 'accounting,': 0.16; 'although,': 0.16; 'cool.': 0.16; 'defined.': 0.16; 'deletes': 0.16; 'fine.': 0.16; 'from:addr:rosuav': 0.16; 'from:name:chris angelico': 0.16; 'headaches': 0.16; 'it".': 0.16; 'it;': 0.16; 'null);': 0.16; 'ought': 0.16; 'pairs': 0.16; 'permanently,': 0.16; 'protecting': 0.16; 'pyqt,': 0.16; 'row)': 0.16; 'sqlite': 0.16; 'stages': 0.16; 'wrestling': 0.16; 'wxpython': 0.16; 'demonstrate': 0.16; 'appropriate': 0.16; 'ignore': 0.16; 'wrote:': 0.18; 'year,': 0.18; 'else,': 0.19; "hasn't": 0.19; 'help.': 0.21; 'command': 0.22; 'select': 0.22; 'programming': 0.22; 'import': 0.22; 'coding': 0.22; 'issue.': 0.22; 'cc:addr:python.org': 0.22; 'error': 0.23; 'issue,': 0.24; 'earlier': 0.24; 'question': 0.24; 'cc:2**0': 0.24; "i've": 0.25; 'references': 0.26; 'tables': 0.26; 'this:': 0.26; 'least': 0.26; 'primary': 0.26; 'defined': 0.27; 'values': 0.27; 'gets': 0.27; 'header:In-Reply-To:1': 0.27; 'tried': 0.27; 'point': 0.28; 'correct': 0.29; '(like': 0.30; 'errors': 0.30; 'ignored.': 0.30; 'said,': 0.30; 'specified': 0.30; 'message-id:@mail.gmail.com': 0.30; 'code': 0.31; 'easier': 0.31; 'lines': 0.31; 'too.': 0.31; 'bunch': 0.31; 'checking.': 0.31; 'keys': 0.31; 'purely': 0.31; 'us!': 0.31; 'violation': 0.31; 'file': 0.32; 'checked': 0.32; 'weeks': 0.32; 'text': 0.33; '-----': 0.33; 'entirely': 0.33; 'actual': 0.34; 'noticed': 0.34; 'table': 0.34; 'maybe': 0.34; "i'd": 0.34; 'could': 0.34; 'problem': 0.35; "can't": 0.35; 'possible.': 0.35; 'but': 0.35; 'received:google.com': 0.35; 'add': 0.35; 'account': 0.65; 'relatively': 0.65; 'worth': 0.66; 'customers': 0.66; 'hours': 0.66; 'here': 0.66; 'importantly,': 0.68; 'records': 0.73; 'foreign': 0.74; 'integrity': 0.74; 'obvious': 0.74; 'discover': 0.82; 'discovered': 0.83; '"hey,': 0.84; '(while': 0.84; '3.4': 0.84; 'fields,': 0.84; 'history,': 0.84; 'laid': 0.84; 'light-weight': 0.84; 'seeing,': 0.84; 'silently': 0.84; 'studying': 0.84; 'terrible': 0.84; 'to:none': 0.92; 'feet': 0.93; 'luck': 0.93; 'you...': 0.96 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:date:message-id:subject:from:cc :content-type:content-transfer-encoding; bh=9q42yaT2/PL4PeC/hnaINvuon0mgIaRbpaul4c+xZi4=; b=u+kigNxpqOYZjiUBvQoxz2qYT//c8ODvw9b3jpq86abkaVzQNO7lzdm6vvbqTMWu6f XgBmRUjY1bNxTRz5C+QuYFzNJm8P7LMuKzd+HkbeTot96sDDY6RC/pQK87V+isPkRAZ+ t3JmlNKjg1/4smUfV88TQ492b1Wkh8r32UYtYSZsxEFODVhumA67uUagfw4knSjPuIM0 CdCRuSOjrJTdqbSk6gDFhk5k4Epr2AHBZ0ZW+uNS419hivxq72XDOtNM1zhSA/ktghis Zfyj348abWK+ULAG7ul6od2dmo5kvEFz0JsGO4GauvF23wejLrSA2qvkrnzpf6NSOMS5 WOCg== MIME-Version: 1.0 X-Received: by 10.42.110.195 with SMTP id r3mr19764349icp.12.1416724336644; Sat, 22 Nov 2014 22:32:16 -0800 (PST) In-Reply-To: References: <5d474a87-e2a5-43d3-b07c-4a1194880b4d@googlegroups.com> <54716987.7040301@gmail.com> Date: Sun, 23 Nov 2014 17:32:16 +1100 Subject: Re: SQLite3 in Python 2.7 Rejecting Foreign Key Insert From: Chris Angelico Cc: "python-list@python.org" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 149 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1416724339 news.xs4all.nl 2893 [2001:888:2000:d::a6]:53486 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:81335 On Sun, Nov 23, 2014 at 5:08 PM, llanitedave wrote: > The application was working "correctly" earlier (meaning that I could ent= er and retrieve data with it; being a strictly user application it didn't a= llow deletes from the GUI), and then I discovered (while cleaning up the us= er documentation) that I'd neglected to include a couple of relatively impo= rtant database fields. Because of SQLite's limited ALTER TABLE capabilitie= s, that mean I had to recreate the tables to add the fields, and in doing s= o noticed that the table in question didn't even have the foreign key const= raint defined. So ever since I defined that constraint, it hasn't let me s= ave any records on that table from Python. Although, as I said, when enter= ing the same data through the Sqliteman application, it works fine. That's = why I suspected that the problem might be in the Python API for SQLite3. > Entirely possible. I never did track down the actual cause of the SQLite3 issues my students were having; though I suspect it's not purely a Python API issue. I tried to demonstrate the concept of foreign keys using the sqlite3 command line tool, and did a sequence of commands which ought to have failed, but didn't. Let's see if I can recreate this: rosuav@sikorsky:~$ sqlite3 SQLite version 3.7.13 2012-06-11 02:05:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> create table foo (val text primary key); sqlite> create table bar (val text references foo on delete set null); sqlite> insert into foo values ('asdf'); sqlite> insert into bar values ('asdf'); sqlite> insert into bar values ('qwer'); sqlite> select * from foo; asdf sqlite> select * from bar; asdf qwer sqlite> delete from foo; sqlite> select * from foo; sqlite> select * from bar; asdf qwer So the foreign key is being completely ignored. If I do the same commands in PostgreSQL, I get errors at appropriate places: rosuav@sikorsky:~$ psql psql (9.3.5) Type "help" for help. rosuav=3D> create table foo (val text primary key); CREATE TABLE rosuav=3D> create table bar (val text references foo on delete set null); CREATE TABLE rosuav=3D> insert into foo values ('asdf'); INSERT 0 1 rosuav=3D> insert into bar values ('asdf'); INSERT 0 1 rosuav=3D> insert into bar values ('qwer'); ERROR: insert or update on table "bar" violates foreign key constraint "bar_val_fkey" DETAIL: Key (val)=3D(qwer) is not present in table "foo". rosuav=3D> select * from foo; val ------ asdf (1 row) rosuav=3D> select * from bar; val ------ asdf (1 row) rosuav=3D> delete from foo; DELETE 1 rosuav=3D> select * from foo; val ----- (0 rows) rosuav=3D> select * from bar; val ----- (1 row) PostgreSQL is a lot more chatty, but what's significant here is that it won't let me insert into the referring table when there's no row in the referent. Also, when I delete the referred-to row, the referring row's key gets correctly set to NULL (like I specified in the constraint definition). I don't know if there's a way to tell SQLite "hey, I want you to actually take notice of foreign keys, tyvm", as there's nothing obvious in the .help command output; but even if there is, I don't know why that isn't the default. Maybe there can be a way to say "ignore foreign key constraints for efficiency", but frankly, I'd rather have constraints actually checked - if you want to cheat them away, actually drop the constraints, don't have the database silently ignore them. > As for Python3, that's a future possibility. My next step was to expand = the functionality of this particular app, which is intended for use in the = field on a tablet or laptop, to a web-app using Django 1.7. WxPython was re= ally a way to get my feet wet on it. The Django version is using Python 3.= 4 and Postgresql 9.3.4, and it's still in the early stages -- I broke off o= f it to correct this mess. > > It's in the back of my head to go back to the field version at some point= with Python3 and PyQt, but it is not this day. Cool. There are several GUI toolkits for Python, and I know multiple of them do support Py3; I can't say which is the best, as I don't do my GUI programming in Python generally. But definitely try to use Python 3 if you can; and try to use PostgreSQL if you can, too. SQLite3 may be the light-weight option, but as you're seeing, it does sometimes take shortcuts; switching to a more full-featured database may be worth doing permanently, or at least for development (think of it like turning on a bunch of assertions). > Anyway, if I can't get this thing straightened out, I may have to just re= move the foreign key constraint and rely on application logic to ensure my = data integrity. :( > > I do appreciate the help, though Chris. If nothing else, you've showed m= e some directions that I needed some extra learning in. My pleasure! Databasing is well worth studying up on; the better laid out your table structure, the easier your coding will be - and more importantly, the easier your data debugging will be. A quick error message about a foreign key violation can save you hours or weeks of headaches down the track when you discover that, for the past year, you had two customers with account number 142857... and then find that there are many such pairs of duplicates, because your application-level code had a concurrency/race issue, and the database wasn't protecting you... This sounds contrived, but it's not unfeasible; I've seen some crazy problems in Pastel Accounting, which (back in the 1990s) used a non-SQL BTrieve back-end with terrible data integrity checking. My dad and I spent many hours wrestling with strange issues, and one of my favourite solutions was "Let's just import that into DB2 real quick, so we can do SQL queries on it". When your acchistl.dat file (that's Accounting, Invoice History, Lines - as opposed to acchisth.dat, which is invoice headers) is 256MB, you don't want to step manually through it. Good luck with the project. If you need help, you know where to find us! ChrisA