Path: csiph.com!v102.xanadu-bbs.net!xanadu-bbs.net!feeder.erje.net!eu.feeder.erje.net!lightspeed.eweka.nl!lightspeed.eweka.nl!newsfeed.xs4all.nl!newsfeed2a.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.005 X-Spam-Evidence: '*H*': 0.99; '*S*': 0.00; 'else:': 0.03; 'schema': 0.05; '(so': 0.07; 'level,': 0.07; 'plenty': 0.07; 'subject:code': 0.07; '+if': 0.09; 'cc:addr:python-list': 0.11; 'python': 0.11; 'up-to-date': 0.14; 'changes': 0.15; '(note': 0.16; 'blocks': 0.16; 'easily,': 0.16; 'from:addr:rosuav': 0.16; 'from:name:chris angelico': 0.16; 'ideally,': 0.16; 'multipart': 0.16; 'patches': 0.16; 'porting': 0.16; 'program?': 0.16; 'well-known': 0.16; 'subject:python': 0.16; 'wrote:': 0.18; 'code.': 0.18; 'solution.': 0.20; 'code,': 0.22; 'memory': 0.22; 'aug': 0.22; 'cc:addr:python.org': 0.22; 'config': 0.24; 'integer': 0.24; 'fairly': 0.24; 'question': 0.24; 'cc:2**0': 0.24; 'handling': 0.26; 'this:': 0.26; 'header:In-Reply-To:1': 0.27; 'rest': 0.29; 'patch': 0.29; "doesn't": 0.30; 'matching': 0.30; 'statement': 0.30; 'message-id:@mail.gmail.com': 0.30; "i'm": 0.30; 'code': 0.31; 'directory,': 0.31; 'once,': 0.31; 'run': 0.32; 'fri,': 0.33; 'period': 0.33; 'table': 0.34; 'basic': 0.35; 'etc': 0.35; 'but': 0.35; 'received:google.com': 0.35; 'add': 0.35; 'version': 0.36; 'really': 0.36; 'next': 0.36; 'should': 0.36; 'level': 0.37; 'sometimes': 0.38; 'version,': 0.38; 'handle': 0.38; 'pm,': 0.38; 'short': 0.38; 'structure': 0.39; 'either': 0.39; 'number,': 0.60; 'break': 0.61; 'new': 0.61; 'numbers': 0.61; 'matter': 0.61; 'simply': 0.61; 'simple': 0.61; 'first': 0.61; 'back': 0.62; "you'll": 0.62; 'such': 0.63; 'levels': 0.65; 'here': 0.66; 'latest': 0.67; 'frank': 0.68; 'real-world': 0.68; 'brand': 0.72; 'end.': 0.84; "it'd": 0.84; 'usage.': 0.84; 'afford': 0.91; 'to:none': 0.92 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; bh=OkdqnylgGsDdbqhvJgiHuCpuEMGlDpo/zhNa4tHvIyM=; b=vBdDSt15cjhAMJKnlUGeq/jo44awn8tA8zViz3LrRRfX5bgE4ZjxtutH53dqowes9+ g9JjUl9MfT75gQ5rklJv2y+cYcEoUUn1A784sZ09nOIWk09Bg4JNCcgvv/GPAKSV5LnN OYIMQJWnwy8TEeMiZ9jOgXCNu0+21Cnkv2gso9L+3Wn8xngZa7NBEBkAvqn+Hq2cKdhD tunNA0kp141JlDDzIw2pA1rNrKOKbvzhjfnmddB9m9YwwsXSM3LnPKyjPrf8LyHReQdT RqVnie3/Eq+jT6UV7+SR8bxp5AD6y6VjO1TTMxdS8XjTDYw3p+S+oJgSNZAPQZDCKTDg p5lQ== MIME-Version: 1.0 X-Received: by 10.50.80.76 with SMTP id p12mr3916830igx.34.1409317310868; Fri, 29 Aug 2014 06:01:50 -0700 (PDT) In-Reply-To: References: Date: Fri, 29 Aug 2014 23:01:50 +1000 Subject: Re: Keeping python code and database in sync From: Chris Angelico Cc: "python-list@python.org" Content-Type: text/plain; charset=UTF-8 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: 62 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1409317314 news.xs4all.nl 2839 [2001:888:2000:d::a6]:38418 X-Complaints-To: abuse@xs4all.nl X-Received-Bytes: 6662 X-Received-Body-CRC: 2062355120 Xref: csiph.com comp.lang.python:77272 On Fri, Aug 29, 2014 at 10:42 PM, Frank Millman wrote: > It is a simple matter to write a program that updates the database > automatically. The question is, what should trigger such an update? My first > thought is to use a version number - store a version number in the working > directory, and have a matching number in the code. If someone downloads the > latest version, the numbers will no longer match, and I can run the upgrade > program. This is a well-known problem, and there's no really perfect solution. The first thing to consider is: What happens if someone back-levels the program? If you can afford to say "never back-level past a schema change", then you can simply version the schema, independently of the code. A simple incrementing integer will do - you don't need a multipart version number. Then you just have code like this: # Get the current schema version, or 0 if there's nothing yet version = db.query("select schema_version from config") if version < 1: # Brand new database db.query("create table blah blah") db.query("create table spam") # etc if version < 2: db.query("alter table spam add whatever") # Add new patch levels here db.query("update config set schema_version = 2") else: throw_really_noisy_error("YOU BACKLEVELLED!") To add a new patch level, you add a new condition with the next number, add its code, and change the update statement at the end. So it'd look like this: if version < 2: db.query("alter table spam add whatever") +if version < 3: + db.query("create table brand_new_table") # Add new patch levels here - db.query("update config set schema_version = 2") + db.query("update config set schema_version = 3") else: throw_really_noisy_error("YOU BACKLEVELLED!") It's fairly straight-forward and readable. You'll sometimes need to go back and defang old patch code (if you simplify or break stuff), and you might prefer to keep your patch 0 handling up-to-date (so it doesn't then have to do all the rest of the patches - have that one immediately set version and bail out), but that's a basic structure that's been proven in real-world usage. (Note that the exact code above might be buggy. I'm recreating from memory and porting to Python at the same time. But the design intent is there.) Ideally, you want to minimize churn. Don't do heaps of schema changes in a short period of time. But this can handle plenty of changes fairly easily, and it'll handle either incremental changes or big blocks of them just the same way (if you upgrade from patch level 10 to patch level 35 all at once, it'll just grind through all those changes one after another). ChrisA