Adding an external encrypted drive with LVM to Ubuntu Linux

I recently added an external eSATA drive to my home computer so I could back up critical data from my home network to one drive. I bought a Western Digital 1TB “green” drive and a Thermaltake external hard drive enclosure with eSATA and USB connectors.

Since my internal hard drives are encrypted it didn’t make sense to back up all of that data to an unencrypted external drive. I’d read Uwe Hermann’s excellent how-to article on disk encryption, but he didn’t cover setting up an LVM partition, which I always use so I can change drive volume sizes on the fly.

This is what I did to set up an external encrypted drive with LVM on an Ubuntu system:

  1. Open a terminal
  2. Get a root prompt:
    sudo /bin/bash
  3. Watch the system log:
    tail -f /var/log/messages
  4. Attach the external drive. The system log tells me that it was detected as /dev/sdc.
  5. Check the drive for bad blocks (takes a couple of hours):
    badblocks -c 10240 -s -w -t random -v /dev/sdc
  6. Write random data to the entire drive. This step takes all night, but it ensures that never-written drive space can’t be differentiated from encrypted data if someone ever tries to crack the drive. (If you’re going to do this, you might as well do it right.)
    shred -v -n 1 /dev/sdc
  7. Create one big LVM partition on the drive using fdisk. Set up one big primary partition /dev/sdc1, set the tag to system id “8e” LVM, and write the changes to disk:
    > fdisk /dev/sdc
    Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
    Building a new DOS disklabel with disk identifier 0xa6846916.
    Changes will remain in memory only, until you decide to write them.
    After that, of course, the previous content won't be recoverable.                                                                                                                   
    
    The number of cylinders for this disk is set to 121575.
    There is nothing wrong with that, but this is larger than 1024,
    and could in certain setups cause problems with:
    1) software that runs at boot time (e.g., old versions of LILO)
    2) booting and partitioning software from other OSs
       (e.g., DOS FDISK, OS/2 FDISK)
    Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)                                                                                                      
    
    Command (m for help): p                                                                                                                                            
    
    Disk /dev/sdc: 999.9 GB, 999989182464 bytes
    255 heads, 63 sectors/track, 121575 cylinders
    Units = cylinders of 16065 * 512 = 8225280 bytes
    Disk identifier: 0xa6846916                                                                                                                                                         
    
       Device Boot      Start         End      Blocks   Id  System                                                                                                                      
    
    Command (m for help): n
    Command action
       e   extended
       p   primary partition (1-4)
    p
    Partition number (1-4): 1
    First cylinder (1-121575, default 1): [ENTER]
    Using default value 1
    Last cylinder, +cylinders or +size{K,M,G} (1-121575, default 121575): [ENTER]
    Using default value 121575
    
    Command (m for help): t
    Selected partition 1
    Hex code (type L to list codes): 8e
    Changed system type of partition 1 to 8e (Linux LVM)
    
    Command (m for help): p
    
    Disk /dev/sdc: 999.9 GB, 999989182464 bytes
    255 heads, 63 sectors/track, 121575 cylinders
    Units = cylinders of 16065 * 512 = 8225280 bytes
    Disk identifier: 0xa6846916
    
       Device Boot      Start         End      Blocks   Id  System
    /dev/sdc1               1      121575   976551156   8e  Linux LVM
    
    Command (m for help): w
    The partition table has been altered!
    
    Calling ioctl() to re-read partition table.
    Syncing disks.
  8. Use cryptsetup to encrypt the drive:
    cryptsetup --verbose --verify-passphrase luksFormat /dev/sdc1
  9. Unlock the drive:
    cryptsetup luksOpen /dev/sdc1 backupexternal
  10. Create the LVM physical volume:
    pvcreate /dev/mapper/backupexternal
  11. Create the LVM volume group:
    vgcreate xbackup /dev/mapper/backupexternal
  12. Create a logical volume within the volume group:
    lvcreate -L 500G -n backupvol /dev/xbackup
  13. At this point you have a device named /dev/xbackup/backupvol, so create a filesystem on the logical volume:
    mkfs.ext3 /dev/xbackup/backupvol
  14. Mount the volume:
    mount /dev/xbackup/backupvol /mnt/backup
  15. To get the volume to mount automatically at boot time add this line to your /etc/fstab file:
    /dev/xbackup/backupvol      /mnt/backup     ext4    defaults        0 5
  16. To be prompted for the decryption key / passphrase at boot time first get the drive’s UUID:
    ls -l /dev/disk/by-uuid

    (In my example I use the UUID for /dev/sdc1)

  17. Then add this line to the /etc/cryptab file:
    ext_drive  /dev/disk/by-uuid/[the UUID of the drive] none luks

That’s it. You now have an external, encrypted hard drive with LVM installed. You’ve created one 500GB volume that uses half the disk, leaving 500GB free for other volumes, or for expanding the first volume.

Hope you find this useful.

Leave a Comment

Pidgin, purple-remote, and solving the “No existing libpurple instance detected” error

I was Googling around tonight to find an answer to a question I was asked about purple-remote and duplicate status messages in the Pidgin Instant Messaging Client and I ran across many, many people posting messages that they couldn’t get purple-remote to work. They had no problem running purple-remote from a command line, but as soon as they tried to call it from a web page or a cron job or from some other process they started seeing the “No existing libpurple instance detected” error.

The solution to this problem is the same as the solution to my original Update Pidgin IM status on Ubuntu using cron article. purple-remote communicates with Pidgin using DBUS. If you’re running purple-remote from a cron job or from some a process that is not owned by you (for instance, an Apache CGI script being run by the “apache” user) then you need to set the DBUS_SESSION_BUS_ADDRESS environment variable for that process so that it matches the DBUS_SESSION_BUS_ADDRESS environment variable for your login. Once that’s done, any processes run by that user that communicate using DBUS will use the same DBUS session that you’re using, so when the “apache” user runs purple-remote it’ll talk to the copy of Pidgin that you are running.

There are many ways you can disclose the value of your login’s DBUS_SESSION_BUS_ADDRESS value to other programs. The easiest way I’ve found is just to save the value to a file and then have your cron job or Apache CGI script execute that file as a script. Check out the Update Pidgin IM status on Ubuntu using cron article for examples of exactly how to do this.

Leave a Comment

One-line search and replace command using Perl

I’ve been using these one-line commands for over a decade. They’re a really easy way to do a search and replace across many files using a Perl regular expression.

This example replaces all instances of the word “old” with the word “new” in “myfile”, saving the old file version as “myfile.bak”:

# perl -i.bak -pe 's/old/new/g;' myfile

This replaces all instances of the word “old” with the word “new” in all of the .html files in the current directory:

# perl -i.bak -pe 's/old/new/g;' *.html

This does the same thing, but also updates all .html files in all subdirectories:

# find . -name '*.html' -exec perl -i.bak -pe 's/old/new/g;' {} \;

Hope you find this useful.

Leave a Comment

Add an entire directory tree to CVS

A web application project I’m working on uses YUI widgets for the user interface and CVS as the version control repository. We needed to keep separate versions of YUI around for development, testing and production, so we separate the versions by their release numbers.

Adding a new YUI build tree to CVS means adding several hundred new files to CVS. Unlike Subversion or Git, if you add a directory to an existing CVS project CVS doesn’t automatically add all of the subdirectories and files. If you try to add all of the files in a subdirectory using “*” you will also get the CVS directory that CVS uses to store information about the directory’s contents, which will generate warning messages from CVS until the end of time.

If you combine the “find” command with “cvs add” you can add all of the subdirectories and files with one command, skipping all of the CVS subdirectories. The syntax is a little arcane, so I thought it was worth posting here.

For the initial setup assume that I have an existing CVS project “apache” with a directory “js”. I add the directory ~/apache/js/yui to my local copy of the project, copy the YUI zip file into the directory, and unzip it. Unzipping it creates the directory “yui” (in this case ~/apache/js/yui/yui) but since I want separate versions of each copy of YUI I rename the directory and call it “2.8.0r4″. Then I add the new directories to CVS.

# mkdir ~/apache/js/yui
# cp yui_2.8.0r4.zip ~/apache/js/yui/
# cd ~/apache/js/yui
# unzip yui_2.8.0r4.zip
# mv ~/apache/js/yui/yui ~/apache/js/yui/2.8.0r4
# cd ~/apache/js
# cvs add yui
# cvs add yui/2.8.0r4

I want to add the directory ~/apache/js/yui/2.8.0r4/build to CVS as well, but I don’t want to add the “~/apache/js/yui/2.8.0r4/examples” or “~/apache/js/yui/2.8.0r4/docs” directories, just ~/apache/js/yui/2.8.0r4/build, so I use a “find” command that specifies just the “build” directory and I prune out any directories containing the name “CVS” from the results returned by find:

# cd ~/apache/js/yui/2.8.0r4/
# for f in `find ./build -name CVS -prune -o -print | sort`; do cvs add $f; done
# cvs ci -m "Adding YUI v2.8.0r4 to the CVS repository"

Piping find’s results to the “sort” command puts the files in order, so directories always get added before the files that they contain.

To use this technique for your own directories, just go to the parent of the directory you just added and use the above “find” command, just substitute the name of your new directory for “./build”.

A side note: We add a symlink that points to the version we want to use at the moment so that the application code can be run with whichever version we want to test with:

# cd ~/apache/js/yui
# ln -sf 2.8.0r4 current

A script tag in the application would look like this:

<script src="/js/yui/current/build/utilities/utilities.js" type="text/javascript" />

Hope you find this useful.

Leave a Comment

Installing Skype on Ubuntu 9.04 using Synaptic

Want to make free phone calls using Skype? Running Ubuntu 9.04? Want to make sure that Skype stays up-to-date with the latest security patches and updates once you install it? Here’s how:

On the Skype Download for Linux page Skype lists Ubuntu 7.04-8.04 as the only supported versions of Ubuntu. You can install Skype on Ubuntu 9.04, but when new updates or security fixes come out you won’t get an automatic update. If you want to make sure that your Skype installation stays up-to-date you should add skype.com as a valid software repository and install Skype from that. If you do this you’ll automatically get updates when they’re available.

To add skype.com as a repository:

  • Start up Synaptic Package Manager. (On Gnome go to System > Administration > Synaptic Package Manager)
  • Select Settings > Repositories
  • Click the Third-Party Software tab
  • Click Add
  • Enter “deb http://download.skype.com/linux/repos/debian/ stable non-free” as the APT line
  • Click the Reload button in the upper left-hand corner to download the list of available files from download.skype.com
  • Type “skype” in the Quick Search box and you should see Skype listed as an installable package
  • Mark it for installation and install

Skype should now appear on your Applications > Internet menu.

Comments (12)

Getting ActiveScaffold to work under Ruby on Rails 2.3

I had started an application using Ruby on Rails and I wanted to try out ActiveScaffold as a replacement for standard scaffolding. I’d seen ActiveScaffold in action before but I’d never used it. I knew that ActiveScaffold dynamically generates application pages using a standard format, and if you change the format it updates all of your application’s pages — unlike standard scaffolding where you have to tweak each and every page. For the application I’m working on it sounded like a huge time-saver, so I decided to try it out.

I was trying to install ActiveScaffold for Ruby on Rails using the steps shown on the Active Scaffold – Getting Started tutorial. However, when I installed ActiveScaffold using the instructions the Mongrel web server I use for development would die as soon as I tried to restart it, dumping a large list of errors:

earl@earl:~/projects/TotalWorldDomination > script/server
=> Booting Mongrel
=> Rails 2.3.2 application starting on http://0.0.0.0:3000
/usr/lib/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/core_ext/
module/aliasing.rb:33:in `alias_method': undefined method `_pick_template'
for class `ActionView::Base' (NameError)
        from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/
core_ext/module/aliasing.rb:33:in `alias_method_chain'

… and the error messages continued, filling up the entire screen.

I quickly figured out that the problem was that I was using Rails 2.3.2, and the release version of ActiveScaffold was for Rails 2.2.x. The tutorial was for Rails 2.2.x, not 2.3.x.

Since I’d just commited all of my changes to my Subversion repository before installing ActiveScaffold I reverted to the previous version of my application, removing any files installed by ActiveScaffold.

I checked the README for ActiveScaffold on GitHub and installed the following branches:

script/plugin install git://github.com/activescaffold/active_scaffold.git -r master
script/plugin install git://github.com/ewildgoose/render_component.git -r rails-2.3

After that Mongrel loads just fine:

earl@earl:~/projects/TotalWorldDomination > script/server
=> Booting Mongrel
=> Rails 2.3.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server

Following the tutorial I added this to my layout:

<%= javascript_include_tag :defaults %>
<%= active_scaffold_includes %>

And I added this to one of my controllers (just so I could test out ActiveScaffold with one model):

  layout "admin"
  active_scaffold

At this point if I try to pull up a view for the controller I just modified I get a “Template is missing – Missing template admin.erb in view path vendor/plugins/active_scaffold/frontends/default/views” error.

Active Scaffold - missing admin view

This error is misleading, since (checking GitHub) there are no admin.* files in active_scaffold/frontends/default/views in any of the four ActiveScaffold branches. It’s not a view that comes built into ActiveScaffold, even though the tutorial makes it sound like a built-in layout, it’s the name of a layout file for your application.

In my case the layout that I’d modified in the previous step was application.html.erb, and it was the only layout in my small application. I made a copy of the file named admin.html.erb:

cd app/views/layouts
cp application.html.erb admin.html.erb

Then following advice I found in the ActiveScaffold forums I commented out everything in all of my standard-scaffold-generated controllers, leaving only the lines:

  layout "admin"
  active_scaffold

… in between the “class” and “end” lines for each controller.

Once I did that ActiveScaffold worked fine.

One note: If you do get any “Request Failed (code 500, Internal Error)” errors, check log/development.log for hints about what’s going on, it’ll usually tell you what the problem is.

Hope you found this useful.

Comments (7)

Samsung SSD drives achieve 2GB/s write speeds

I spend a fair amount of my time administering large databases and trying to squeeze the maximum speed out of computer hardware. The types of databases I work on are typically in the 1-2TB range and the disks that they run on have to support a mix of bulk writes, bulk deletes, random writes and random reads. Some of the systems I work on can burst at up to 500MB/s and sustain random write speeds of 250MB/s. Your typical SATA2 drive for home use supports a maximum sequential write speed of around 60MB/s, and they run slower still when doing lots of random IO, so getting 250MB/s sustained random write speeds requires some special hardware.

This is why I was so impressed with a demo I saw of the new Samsung SSD (solid state drive). Since these drives are solid state — no moving parts — they have 220MB/s sequential read and 200MB/s sequential write speeds. If you tie a bunch of them together in a RAID configuration you can speed writes up even more, which is what the guy in the video below did. The end result was a desktop server that could read and write sequential data at 2GB/s.

Usually when I see demos like this the caveat is that the total amount of space available is 80MB or so. Not the case here. These new drives are 256GB each, and the RAID array built below has a total capacity of 6TB.

Check this out. If you’re into high-speed hardware using off-the-shelf parts, this is just incredible.

One thing to note, the demo does not show random read/write speeds. All of the guy’s demonstrations are of sequential IO speed. This is significant, because in the past SSD drives slow down significantly if they’re asked to do random IO, and you want lots of speed for random IO if you want to use these drives for database applications.

Comments (1)

Upgrading Blu-ray Player Firmware from Linux

I’ve got an older Sony Blu-ray player, a BDP-S300 model. I was trying to watch Burn After Reading tonight and the movie would not start. Not because the disc was damaged, but because it had some embedded software that didn’t work with the Blu-ray player’s old firmware — software that it used to START PLAYING THE MOVIE.

So I went to Google, typed in “Sony Blu-ray BDP-S300 firmware download” and found links to the usual suspects, Blu-ray.com and esupport.sony.com. Both sites have the latest firmware for Blu-ray players. Both have the software in an ISO CD-ROM image format, so you can burn a CD-ROM with the firmware, boot your Blu-ray player with the CD, and in 10 to 20 minutes the firmware has been upgraded. (Yes, 10-20 minutes. The firmware update process is even slower than the painfully, glacially slow disc load time for Sony Blu-ray players.)

The only problem is that both sites compress the ISO file into a Windows-based EXE file! Apparently the people at Sony don’t realize that there are people out there with Macs, so Mac users are screwed. They also don’t appear to realize that they could make the same file available as a plain ISO file and ANYONE with ANY OS with an ISO CD-burning software package and a CD-ROM burner (pretty much every personal computer made in the last 15 years) could make a CD-ROM from the file.

I have no Windows box at the moment, I do most everything on Linux, so after firing off a customer feedback letter to Sony slamming them for their short-sightedness I went ahead and clicked the pull-down menu that tells their site that yes, I was using Windows 2000, please let me download the damn file, and I downloaded UPDATE_BDPS300_VER0450.EXE.

After that it was a matter of typing:

apt-get install wine
mkdir ~/tmp/bluray
wine UPDATE_BDPS300_VER0450.EXE

A pop-up box asked me where to put the files it was about to extract from UPDATE_BDPS300_VER0450.EXE, so I clicked the Browse button and selected ~/tmp/bluray. A few seconds later UPDATE_BDPS300_VER0450.ISO was in the ~/tmp/bluray directory. I fired up K3b, clicked on the left-side file menu tree to get to ~/tmp/bluray, double-clicked the UPDATE_BDPS300_VER0450.ISO file, and burned the CD-ROM.

I stuck the CD-ROM in the Blu-ray player and about 15 minutes later the player spit the disc back out.

Now I can watch the movie…

Comments (2)

Renaming a CVS Branch

I recently needed to rename a branch in a CVS repository, and after Googling around for a while I found only hints of what might work, but no actual examples of how to rename a branch. Apparently it’s one thing to rename a tag in CVS, but if it’s a branch tag CVS doesn’t give you any easy ways to rename the branch, and if you try “normal” rename commands on a branch tag you’ll start seeing cryptic error messages. I started fiddling around with some of the commands in a CVS sandbox and figured out how to do it.

If you give a branch the wrong name you can fix the name by adding the good name as a tag name on the same branch, changing your working version to the good name, then deleting the old tag, and finally converting the new tag into a branch tag.

Whoops! I created a bad tag name:

earl@earl:~/sandbox/earl > cvs tag -b bad_name cvs_test.pl
T cvs_test.pl

earl@earl:~/sandbox/earl > cvs up -r bad_name cvs_test.pl
M cvs_test.pl

earl@earl:~/sandbox/earl > cvs ci cvs_test.pl
/cvsroot/sandbox/earl/cvs_test.pl,v  <--  cvs_test.pl
new revision: 1.1.2.1; previous revision: 1.1

earl@earl:~/sandbox/earl > cvs stat cvs_test.pl
===================================================================
File: cvs_test.pl       Status: Up-to-date

   Working revision:    1.1.2.1
   Repository revision: 1.1.2.1 /cvsroot/sandbox/earl/cvs_test.pl,v
   Commit Identifier:   21af497a241a4567
   Sticky Tag:          bad_name (branch: 1.1.2)
   Sticky Date:         (none)
   Sticky Options:      (none)

To fix that, I tag the working revision (in this case 1.1.2.1) of the bad_name branch with the good_name and then switch to the good_name branch:

earl@earl:~/sandbox/earl > cvs admin -N good_name:1.1.2.1
cvs admin: Administrating .
RCS file: /cvsroot/sandbox/earl/cvs_test.pl,v
done

earl@earl:~/sandbox/earl > cvs stat cvs_test.pl
===================================================================
File: cvs_test.pl       Status: Up-to-date

   Working revision:    1.1.2.1
   Repository revision: 1.1.2.1 /cvsroot/sandbox/earl/cvs_test.pl,v
   Commit Identifier:   21af497a241a4567
   Sticky Tag:          bad_name (branch: 1.1.2)
   Sticky Date:         (none)
   Sticky Options:      (none)

earl@earl:~/sandbox/earl > cvs up -r good_name cvs_test.pl
earl@earl:~/sandbox/earl > cvs stat cvs_test.pl
===================================================================
File: cvs_test.pl       Status: Up-to-date

   Working revision:    1.1.2.1
   Repository revision: 1.1.2.1 /cvsroot/sandbox/earl/cvs_test.pl,v
   Commit Identifier:   21af497a241a4567
   Sticky Tag:          good_name (revision: 1.1.2.1)
   Sticky Date:         (none)
   Sticky Options:      (none)

Next I delete the bad_name tag by using the -n option without specifying a revision number:

earl@earl:~/sandbox/earl > cvs admin -n bad_name
cvs admin: Administrating .
RCS file: /cvsroot/sandbox/earl/cvs_test.pl,v
done

earl@earl:~/sandbox/earl > cvs stat cvs_test.pl
===================================================================
File: cvs_test.pl       Status: Up-to-date

   Working revision:    1.1.2.1
   Repository revision: 1.1.2.1 /cvsroot/sandbox/earl/cvs_test.pl,v
   Commit Identifier:   21af497a241a4567
   Sticky Tag:          good_name (revision: 1.1.2.1)
   Sticky Date:         (none)
   Sticky Options:      (none)

earl@earl:~/sandbox/earl > cvs up -r bad_name cvs_test.pl
cvs update: `cvs_test.pl' is no longer in the repository

The bad tag name is gone! To get the good tag again:

earl@earl:~/sandbox/earl > cvs up -r good_name cvs_test.pl
U cvs_test.pl

earl@earl:~/sandbox/earl > cvs stat cvs_test.pl
===================================================================
File: cvs_test.pl       Status: Up-to-date

   Working revision:    1.1.2.1
   Repository revision: 1.1.2.1 /cvsroot/sandbox/earl/cvs_test.pl,v
   Commit Identifier:   21af497a241a4567
   Sticky Tag:          good_name (revision: 1.1.2.1)
   Sticky Date:         (none)
   Sticky Options:      (none)

The last step is to convert the good_name tag into a branch tag:

earl@earl:~/sandbox/earl > cvs tag -d good_name cvs_test.pl
D cvs_test.pl

earl@earl:~/sandbox/earl > cvs tag -b good_name cvs_test.pl
T cvs_test.pl

earl@earl:~/sandbox/earl > cvs up -r good_name cvs_test.pl

earl@earl:~/sandbox/earl > cvs ci cvs_test.pl
/cvsroot/sandbox/earl/cvs_test.pl,v  <--  cvs_test.pl
new revision: 1.1.2.1.2.1; previous revision: 1.1.2.1

There may be an easier way to do this, but this does work.

Hope you found this useful.

Comments (3)

Creating BETTER auto-increment columns in Oracle

I was explaining to a developer today how Oracle doesn’t support auto-increment columns for IDs and how you have to implement your own. I wanted to point him to a tutorial but I found that most of the tutorials have PL/SQL code with unexplained side effects. So I thought I’d post some Oracle PL/SQL code that behaves just like a MySQL or PostgreSQL auto-increment column, but which also fixes the sequence number when an application provides its own ID number for a record, which is one place where MySQL and PostgreSQL autoincrement columns fail.

Step 1: Know the names of your tablespaces. Know which tablespace you want to use for your tables and which tablespace you want to use for your indexes. Don’t assume that Oracle is going to put the table in the right place. To get a list of available tablespaces:

select tablespace_name from user_tablespaces;

Step 2: Create your table. In this example the table is built in the tablespace TDATA and the primary key index is built in the tablespace INDX.

create table example (
    id number not null,
    name varchar2(30),
    constraint example_pk primary key(id) using index tablespace INDX
) tablespace TDATA;

Note that by declaring the id column “not null” you can’t add a record with an empty ID field, ever.

Creating the primary key constraint on a separate line allows us to name the constraint, which can be handy when you’re looking through the data dictionary later on trying to figure out which constraint does what.

Step 3: Create a sequence. In this case I create an ID sequence that starts at 1 and goes on from there. If you want to start your ID sequence with a different number, or increment by some other amount, change these values.

create sequence example_id_seq start with 1 increment by 1;

Step 4: Create a trigger. This is what does the actual work setting the ID number. There are many ways to do this, here are two:

Method 1: Use a sequence-generated ID for all IDs.

create or replace trigger example_insert
before insert on example
for each row
begin
    select example_id_seq.nextval into :new.id from dual;
end;
/

Method 1 will always use a sequence-generated ID no matter what. For example:

SQL> insert into example (id, name) values (100, 'Earl');
1 row created.
SQL> insert into example (name) values ('Cleopatra');
1 row created.
SQL> insert into example (name) values ('Frankenstein');
1 row created.
SQL> select * from example;
        ID NAME
---------- ------------------------------
         1 Earl
         2 Cleopatra
         3 Frankenstein

In this case the trigger-supplied ID 1 overwrote the user-supplied id 100.

Method 2: Allow users to supply their own IDs when they want to.

create or replace trigger example_insert
before insert on example
for each row
declare
    max_id number;
    cur_seq number;
begin
    if :new.id is null then
        -- No ID passed, get one from the sequence
        select example_id_seq.nextval into :new.id from dual;
    else
        -- ID was set via insert, so update the sequence
        select greatest(max(id), :new.id) into max_id from example;
        select example_id_seq.nextval into cur_seq from dual;
        while cur_seq < max_id
        loop
            select example_id_seq.nextval into cur_seq from dual;
        end loop;
    end if;
end;
/

Method 2 will use the sequence-generated ID if the insert statement doesn’t supply an ID, which is what MySQL and PostgreSQL do. It also updates the sequence so that the next value of the sequence won’t collide with IDs supplied by an insert statement.

For example, let’s say I’m using Trigger Method 2 and just loaded the table with a bunch of old data, including old ID numbers.

SQL> delete from example;
3 rows deleted.
SQL> insert into example (id, name) values (200, 'Cleopatra');
1 row created.
SQL> insert into example (id, name) values (300, 'Frankenstein');
1 row created.
SQL> insert into example (name) values ('Earl');
1 row created.
SQL> select * from example;

        ID NAME
---------- ------------------------------
       200 Cleopatra
       300 Frankenstein
       301 Earl

The last record added gets an ID of 301.

Hope you find this useful.

Comments (13)